Sie sind auf Seite 1von 124

Handout Programmentwicklung II -- C und C++

1



 





WhD^<^



Teil 1: Strukturierte Programmierung in ANSI C .................6


1.1 Einfhrung ....................................................................6
1.1.1 Basiseigenschaften der Programmiersprache C ......6
1.1.2 Umgang mit dem Compiler gcc ..............................10
1.1.3 Einfache Ein-/Ausgabefunktionen zum Testen von
C-Programmen ..................................................................20
1.1.4 Anwendung des C-Prprozessors PrprozessorDirektiven #include und #define ........................................35
1.1.5 Das Werkzeug make Automatisierung der
Programmgenerierung ......................................................39
1.2 Grundlagen der Programmiersprache C ....................52
1.2.1 Variablen und Konstanten ......................................52
1.2.2 Datentypen .............................................................59
1.2.3 Vereinbarungen und Gltigkeitsbereiche ...............64
1.2.4 Operatoren .............................................................69
1.2.5 Selbstlern-Kapitel: Blcke und Kontrollstrukturen ..80
1.2.6 Funktionen und Programmstruktur .........................85
2 Fortgeschrittene Sprachkonstrukte .................................104
2.1 Arrays .......................................................................104
2.1.1 Eindimensionale Arrays (Vektoren) ......................104
2.1.2 Strings (Zeichenketten) ........................................112
2.1.3 Mehrdimensionale Arrays .....................................120
2.2 Zeiger und dynamische Speicherverwaltung ...........126
2.2.1 Speicherverwaltung 1 Zeiger, Adressen und
Zeigerarithmetik ..............................................................126
2.2.2 Speicherverwaltung 2 Zeiger und Arrays ..........132
2.2.3 Allokation von Zeigern ..........................................135
2.2.4 Speicherverwaltung 3: Arrays aus Zeigern ..........140
2.2.5 Speicherverwaltung 4: Modellierung
mehrdimensionaler Arrays mit Zeigern ..........................150
2.3 Zusammengesetzte Datenstrukturen Teil II ..........158
2.3.1 Strukturen 1 - Vereinbarung .................................158
2.3.2 Strukturen 2 bergabe an Funktionen ..............165
2.3.3 Strukturen 3 Arrays aus Strukturen und Zeiger auf
Strukturen ........................................................................168
2.3.4 Vereinbarung von Strukturen mit typedef ..........182
2.4 Varianten (Unions) ...................................................184
Prof. Dr. U. Matecki

Seite

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.5 Aufzhlungen (enumeration) ....................................187


2.6 Dateien in C .............................................................188
2.6.1 Der FILE-Pointer ffnen und Schlieen von
Dateien ............................................................................189
2.6.2 Datei Ein-/Ausgabe ...........................................192
2.7 Zeiger auf Funktionen (Function Pointer) ................204
2.7.1 Grundlagen ...........................................................204
2.7.2 Simulation von OOP in C: Funktionszeiger in
Strukturen ........................................................................210
3 Teil 2: Objektorientierte Programmierung in ANSI C++ ..218
3.1 Handwerkliches: Umgang mit dem Compiler "g++" .218
3.2 Grundlagen der Programmiersprache C++ ..............219
3.2.1 Vergleich mit C / Wichtigste Unterschiede und
Gemeinsamkeiten ...........................................................219
3.2.2 Grundlagen der Sprache C++ ..............................221
3.2.3 Einbinden der Standard Template Library (STL)..238
3.2.4 berladen von Operatoren -- eine Einfhrung .....240
4 Literatur/Quellen ..............................................................247

Vorwort
Dieses Handout ist als Begleitunterlage des Vorlesungsanteils
"C/C++" der Lehrveranstaltung "Programmentwicklung II" entstanden. Fr viele hilfreiche Hinweise zur Verbesserung der
Vorlesung und der zugehrigen Unterlagen mchte ich mich bei
den Studierenden der vergangenen Semester an dieser Stelle
recht herzlich bedanken.
Vor Beginn der Vorlesung einige Hinweise zum erfolgreichen
Absolvieren dieser Lehrveranstaltung:
 Viele Kapitel enthalten Programmbeispiele, die auch im
Quelltext auf meinem Freigabeverzeichnis unter dem im
Handout angegebenen Unterverzeichnis liegen.
Diese Programmbeispiele sollten unbedingt nach der Vorlesung am Rechner ausgetestet und durch kleine Modifikationen nachvollzogen werden. Hierbei ist es sehr hilfreich, Zwischenergebnisse der Programme mit printf()
auf den Bildschirm auszugeben.
 Die blau eingefrbten Unterkapitel sind zum Selbst-Erarbeiten gedacht. Sie sollten nach Dozentenhinweis selbststndig erarbeitet werden. Gegebenenfalls sollten Fragen
hierzu in der darauf folgenden Vorlesungsstunde gestellt
werden. Hilfreich ist es auch hier, die Programmbeispiele
selbst zu testen, da hier manchmal das Programmverhalten anders ist, als auf den ersten Blick erwartet.
 Bei komplizierteren Programmen, z. B. Sortieralgorithmen
oder bei Programmen mit Zeigern (Pointern) ist es sinnvoll, kleinere Ablaufszenarien mit konkreten Daten (z. B.
konkreten Zahlen oder Zeichenketten) "auf dem Papier"
durchzurechnen.

Prof. Dr. U. Matecki

Seite

Prof. Dr. U. Matecki

Seite

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Die Programmbeispiele wurden mit gcc unter UbuntuLinux bersetzt und getestet.Diese Programmierumgebung, sowie Visual Studio 2005 werden auch im
Praktikum verwendet werden.

1 Teil 1: Strukturierte Programmierung in ANSI C

 Falls die Syntax von C noch etwas Schwierigkeiten bereitet: Geben Sie die Beispiel-Programme der Vorlesung anfangs "von Hand" ein und versuchen Sie, sie selbststndig
mit gcc zum Laufen zu bringen.

1.1

 Ein Vorab-Hinweis zur Klausur: Die Praktikumsaufgaben


sollte jedes Team und jedes Team-Mitglied grndlich und
selbststndig bearbeiten, da in der Klausur ebenfalls
selbststndige Programm-Entwicklung gefordert wird. Je
mehr Programmierpraxis Sie am Ende des Semesters
haben, desto besser sind die Erfolgs-Aussichten .

Einfhrung

1.1.1 Basiseigenschaften der Programmiersprache C


 Wichtigster Unterschied zu Java:
Nicht objektorientiert, sondern strukturiert.
 Wir haben keine Klassen mit Attributen und Zugriffsmethoden (Datenkapselung), sondern nur eine strukturierte
Aufteilung der Aufgaben eines Programms in Unteraufgaben (Funktionen).

 Ein Hinweis zu den unterschiedlichen Standards der


Programmiersprache C:
o Compiler, die nach dem heute noch gebruchlichen
C89-Standard (ISO/IEC 9899:1989) arbeiten, werden mehr und mehr durch die 1999 eingefhrten Erwieterungen des C99-Standards ergnzt (ISO/IEC
9899:1999).

 Kommentare in C: werden in /* */ eingeschlossen. Ab C99Standard sind auch die in Java und C++ gebruchlichen
Doppel-Schrgstriche erlaubt.
//
//
//
//

o Alle in diesem Skript nicht explizit gekennzeichneten


Stellen enthalten Code, der bereits im C89-Standard gebruchlich war. Dort, wo Gebrauch gemacht
wird von C99-Erweiterungen, die von gcc bereits
zugelassen werden, ist dies gesondert gekennzeichnet.

fuer einzeilige Kommentare


erlaubt
Sie werden jedoch noch nicht von jedem
C-Compiler akzeptiert

/* Daher verwenden wir // in PE 2 in der


Programmiersprache C noch nicht */

Viel Spa und Erfolg beim Durcharbeiten der Beispiele und


beim Erarbeiten der Praktikumsaufgaben!!

Albstadt, im September 2009


Prof. Dr. U. Matecki

Ute Matecki
Seite

Prof. Dr. U. Matecki

Seite

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Empfehlungen zur Lesbarkeit von C-Programmen:

sind 2 verschiedene Variablen.


 In C89-konformen C-Programmen mssen lokale
Variablen immer zu Beginn eines Blockes z. B. zu
Beginn eines Funktionsrumpfes vereinbart werden:

o Durch Leerzeichen sinnvoll gliedern


o Nur ein Statement pro Zeile eines Programms

int rechne()
{
int i=0, j=2;
i += 3;
j += i;
return j;
}

o Jede geschweifte Klammer { oder } auf eine Zeile


 Java-Stil bzw. C++-Stil:
while (i++ < 10) {
tuwas();
}
 C-Stil:
while (i++ < 10)
{
tuwas();
}
o Nach jeder geffneten geschweiften Klammer { um 2
Zeichen einrcken
o Falls ein Befehl (z. B. Funktionsaufruf) ber mehrere
Zeilen geht: die nachfolgenden, dazugehrenden
Zeilen einrcken
o Wo notwendig, Programme durch Kommentare erlutern (lieber zuviel als zuwenig).
 C ist wie Java - case-sensitive, d. h. Gro- und Kleinbuchstaben werden als voneinander verschiedene
Zeichen behandelt:
int meinevariable;
int meineVariable;

Prof. Dr. U. Matecki

Seite

Prof. Dr. U. Matecki

Seite

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Der von uns verwendete gcc-Compiler lsst jedoch auch


frei platzierte Vereinbarungen zu, wie sie ab dem C99Standard erlaubt sind:

1.1.2 Umgang mit dem Compiler gcc


Erstes Ziel: Ein Hello-World-Programm zum Laufen bekommen.Hierzu mssen wir

int rechne()
{
int i=0;
i += 3;
int j = 2;
j += i;
return j;
}

 Das C-Programm schreiben (C-Quelldatei erzeugen)


 Falls das C-Programm aus einer einzigen Quelldatei besteht:
o Direktes bersetzen des C-Programms in lauffhigen
Maschinencode:

ACHTUNG: Diese Art der Vereinbarung kann Quelltexte beliebig unlesbar machen und ist daher in unserem Praktikum nicht erlaubt!

Prof. Dr. U. Matecki

Seite

hello.c:
int main()
{
// ein schoenes
// C-Programm
.....
.....
}

Prof. Dr. U. Matecki

hello:
gcc o hello hello.c

00111011011...

Seite

10

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Falls das C-Programm aus mehreren Quelldateien besteht, die jeweils verschiedene Unteraufgaben enthalten:

Beispiel 1:
Schritt 1: C-Programm schreiben.

o bersetzen der einzelnen C-Quelldateien in Objektcode. Hierbei entsteht fr jede C-Quelldatei eine separate Objektdatei mit Maschinencode.
o Binden der Objektdateien zu einem lauffhigen Programm:
C-Quelldateien:

Binre Objektdateien mit


Maschinencode:

main.c:
int main()
{
// ein schoenes
// C-Programm
int i = rechne();
}

hello1/hello1.c:
/* Dies ist ein Kommentar
*
*/
#include <stdio.h>

main.c

Info zur Standard-Ein/Ausgabe-Bibliothek einfgen

int main()

Funktion namens main definieren, die keine Argumentwerte empfngt und einen ganzzahligen
Wert zurckgibt

Die Anweisungen der Funktion main stehen in


geschweiften Klammern

Ausfhrbare
Programmdatei:

main ruft die Bibliotheksfunktion printf auf, um


diese Zeichenfolge zu drucken. \n stellt den
Zeilentrenner dar.

main.o:
gcc c

Ein Kommentar wird in /* */ eingerahmt

printf("Hallo Mitglieder der LVA Programmentwicklung 2\n");

001011...

rechne:
return 0;
001011...

gcc o rechne main.o rechne.o

main gibt den Wert 0 zurck

11101...

rechne.c:
int rechne()
{
return 3*4;
}

Erste 3 Zeilen: Kommentare in C werden in /* */ eingeschlossen.

rechne.o:
gcc c

rechne.c

11101...

Im Gegensatz zu Java ist main in C nicht die Methode einer


Klasse, sondern eine Funktion, die fr sich allein steht.
Ein C-Programm besteht im allgemeinen aus Funktionen und
Variablen.
Eine Funktion besteht aus Anweisungen, die definieren, welche
Aktionen ausgefhrt werden sollen.

Prof. Dr. U. Matecki

Seite

11

Prof. Dr. U. Matecki

Seite

12

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die Variablen enthalten die Werte, die whrend der Programmausfhrung benutzt werden sollen.
Im obigen Beispiel ist main eine solche Funktion. In C ist main
jedoch fr einen ganz bestimmten Zweck reserviert: Sie ist die
Funktion, bei der der gesamte Programmablauf startet. Das
bedeutet, da jedes C-Programm genau eine main-Funktion
haben mu.
Die Anweisung #include <stdio.h> bewirkt, da der
Compiler eine Datei namens stdio.h mit Informationen z. B.
ber die von uns genutzte printf-Funktion in unseren
Quelltext einfgt.
Das bedeutet, da der Compiler sich eine Art ZwischenQuelldatei erzeugt, in die der gesamte Inhalt der Datei
stdio.h hineinkopiert wird.
Aus dieser Zwischendatei wird anschliessend das lauffhige
Programm erzeugt. Die spitzen Klammern <> sagen aus, da
der Compiler diese Datei in einem dem Compiler bekannten
Standard-Pfad findet.

Die Zeichenfolge \n ist die C-Schreibweise fr den Zeilentrenner. Dieser bewerkstelligt, dass die auf ihn folgende
Ausgabe am linken Rand in der nchsten Zeile fortgesetzt wird.
Schritt 2: bersetzen des C-Programms
Um die obige Quelldatei hello1.c unter Linux in ein
lauffhiges Programm zu bersetzen, gehen wir folgendermaen vor:
bersetzungsvorgang:
gcc -o hello1 hello1.c
bersetzt die Quelldatei in das ausfhrbare Programm hello1.
- gcc ist der Name des Compiler-Programms
- die Option o gibt den gewnschten Namen des bersetzten Programms an.
Der Aufruf
hello1

Die geschweiften Klammern {} umgeben (wie in Java) die


Anweisungen, aus denen die Funktion (in Java: die Methode)
besteht.

bewirkt folgende Ausgabe:


Hallo Mitglieder der LVA Programmentwicklung 2

Die Funktion main enthlt nur eine einzige Anweisung, den


Aufruf der Funktion printf.
Eine Funktion wird aufgerufen, indem man ihren Namen angibt,
gefolgt von einer Liste von Argument-Werten in Klammern.
Hier: eine in Doppelanfhrungszeichen angegebene, konstante
Zeichenkette.

Prof. Dr. U. Matecki

Seite

13

Prof. Dr. U. Matecki

Seite

14

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel 2: Ein Hello-World-Programm, bestehend aus mehreren Quelldateien.


Wir tun im nachstehenden Programmtext folgendes:
Schritt 1:

 Schreiben einer neuen main()-Funktion, die die selbstgebaute hello2()-Funktion aufruft. Die neue main-Funktion
wird in einer separaten Quelldatei hello2.c abgespeichert.
hello2/hello2.c:

 Schreiben einer eigenen hello2()-Funktion. Diese wird


in einer separaten Quelldatei hello2Funcs.c abgespeichert.

#include "hello2Funcs.h"

Info zu unserer hello2 - Funktion einfgen

int main()

Funktion namens main definieren, die keine Argumentwerte empfngt und einen int-Wert zurckgibt
Die Anweisungen der Funktion main stehen in
geschweiften Klammern

hello2/hello2Funcs.c:
hello2();

#include <stdio.h>

return 0;

void hello2()

main ruft die von uns definierte Funktion hello2


auf.

Info zur Standard-Ein/Ausgabe-Bibliothek einfgen

Funktion namens hello2 definieren, die keine Argumentwerte empfngt keinen Wert zurckgibt

main gibt den int-Wert 0 zurck

Die Anweisungen der Funktion hello2 stehen in


geschweiften Klammern
hello2 ruft die Bibliotheksfunktion printf auf, um
diese Zeichenfolge zu drucken. \n stellt den
Zeilentrenner dar.
printf("Hallo Mitglieder der LVA Programmentwicklung 2\n");

Die #include-Anweisung zu Beginn des Programms


sieht hier anders aus:
Die doppelten Anfhrungszeichen sagen dem Compiler,
da er die Header-Datei hello2Funcs.h nicht in seinem
Standard-Pfad suchen soll, sondern im aktuellen Verzeichnis, von dem aus er aufgerufen wird. Dies ist in der
Regel das Verzeichnis, in dem sich auch unsere selbstgeschriebenen Quelldateien befinden.
Wir binden hier eine selbstgebaute Header-Datei
hello2Funcs.h ein, die unserer main()-Funktion sagt,
wie die hello2-Funktion aussieht.
Die main()-Funktion ruft nun unsere selbstgebaute Funktion hello2() auf. Danach gibt sie den Wert 0 zurck.

Prof. Dr. U. Matecki

Seite

15

Prof. Dr. U. Matecki

Seite

16

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Schreiben der dazugehrenden Headerdatei hello2Funcs.h Diese dient dazu, unserer main()-Funktion
Informationen ber unsere hello2()-Funktion
mitzugeben.

Schritt 2:
 bersetzen der Quelldatei hello2.c. Ergebnis: Objektdatei mit Maschinencode hello2.o
 bersetzen der Quelldatei hello2Funcs.c. Ergebnis:
Objektdatei mit Maschinencode hello2Funcs.o

hello2Funcs.h:

void hello2();

Deklaration des Funktionsprototypen unserer


hello2-Funktion.
Auf diese Weise wissen aufrufende Funktionen,
wie sie hello2 zu nutzen haben. (Parameter und
Rckgabewerte)

 Zusammenbinden der beiden Objektdateien zu einem ausfhrbaren Programm.


bersetzungsvorgang:
gcc -c hello2.c
gcc -c hello2Funcs.c
bersetzt die Quelldateien in sog. Objektdateien mit dem bersetzten Maschinencode
der einzelnen Quelldateien. Dies wird durch die Option c bewirkt.
Ergebnis: Objektdateien
hello2.o
hello2Funcs.o
Der nachfolgende Aufruf
gcc -o hello2 hello2.o hello2Funcs.o
bewirkt, dass die beiden Objektdateien zum ausfhrbaren Programm hello2
zusammengebunden werden.
Der Aufruf
hello2
bewirkt die Ausgabe
Hallo Mitglieder der LVA Programmentwicklung 2

Prof. Dr. U. Matecki

Seite

17

Prof. Dr. U. Matecki

Seite

18

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Achtung: Die Header Datei hello2Funcs.h muss nicht separat bersetzt werden. Dies geschieht automatisch, da der Compiler ihren Text durch die #include-Anweisung an deren Stelle
in den Text der Quelldatei hello2.c kopiert.

1.1.3 Einfache Ein-/Ausgabefunktionen


zum Testen von C-Programmen
Dieses Kapitel zeigt Ihnen, wie sie errechnete Variablenwerte
auf den Bildschirm ausgeben knnen und wie Sie ber Tastatur
eingegebene Werte in Variablen einlesen knnen.

1.1.3.1 Formatierte Ausgabe von Variablenwerten mit printf()


Bisher wurde die Funktion printf() nur verwendet, um feste
Zeichenketten wie Hello World auf den Bildschirm auszugeben.
Weitaus hufiger wird diese Funktion jedoch genutzt, um im
Programm-Ablauf errechnete Werte von Variablen auszugeben.
Hierzu muss printf() jedoch mitgeteilt bekommen, um was
fr Datentypen es sich bei den auszugebenden Variablen handelt. Diese Mitteilung geschieht ber den so genannten Formatstring:
hello3/hello3.c:

#include <stdio.h>
int main()
{
/*Wir lernen Konstanten erst spaeter kennen;
*daher werden im Folgenden
*nur einfache lokale Variablen verwendet
*/
int nummer = 2;
float float_wert = 1.74f;
char char_wert = 'a';

Prof. Dr. U. Matecki

Seite

19

Prof. Dr. U. Matecki

Seite

20

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

/* Strings bzw. Zeichenketten sind in C nur als


* char-Arrays verfuegbar.
* Im Augenblick bitte einfach hinnehmen;
* die Besonderheiten von char* Arrays werden spaeter sehr genau erklaert.
*/
char hello[] = "Hallo Mitglieder der LVA PE";
char das_ist_float[] = "Das ist ein float: ";
char das_ist_char[] = "Das ist ein char";

Genauere Betrachtung der drei printf()-Aufrufe:


Betrachtung der printf()-Anweisungen
...
...
printf("%s %d\n\n", hello, nummer);
...
1 eingefgtes
EingefgteLeerzeichen Eingefgter int-Wert aus
2 eingefgte
Teil-Zeichenkette aus Variable nummer.
Zeilenumbrche
Variable hello
(Variable wird als int
interpretiert und als
(Variable wird als
String interpretiert) Zeichenkette umformatiert)

printf("%s %d\n\n", hello, nummer);


printf("%s %f\n\n", das_ist_float, float_wert);
printf("%s %c\n\n", das_ist_char, char_wert);
return 0;

Ergebnis des printf-Aufrufs:

Der erste bergebene Parameter von printf() ist eine in


-Anfhrungszeichen eingeschlossene Zeichenkette. In ihr
sind mit %-Zeichen eingeleitete Formatanweisungen zu sehen.
Daher heit dieser erste Parameter auch Formatstring. Die
Formatanweisungen sagen aus, als was die weiter hinten
angegebenen Variablen zu interpretieren sind.

Prof. Dr. U. Matecki

Seite

21

Prof. Dr. U. Matecki

Seite

22

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Betrachtung der printf()-Anweisungen


...
...
printf("%s %f\n\n", das_ist_float, float_wert);

Betrachtung der printf()-Anweisungen


...
...
printf("%s %d\n\n", das_ist_char, char_wert);

...

...

1 eingefgtes
EingefgteLeerzeichen Eingefgter float-Wert.
2 eingefgte
Teil-Zeichenkette
(Variable wird als float
Zeilenumbrche
(Variable wird als
interpretiert und als
String interpretiert) Zeichenkette umformatiert)

1 eingefgtes
EingefgteLeerzeichen Eingefgter char-Wert.
2 eingefgte
Teil-Zeichenkette
(Variable wird als char
Zeilenumbrche
(Variable wird als
interpretiert und als
String interpretiert) Zeichenkette umformatiert)

Ergebnis des printf-Aufrufs:


Ergebnis des printf-Aufrufs:

Prof. Dr. U. Matecki

Seite

23

Prof. Dr. U. Matecki

Seite

24

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Anbei zur Vervollstndigung eine Tabelle mit den gngigsten


Formatanweisungen fr printf():
Format
zeichen

Bedeutung

%d, %i

Argument wird dezimal, ganz- int


zahlig ausgegeben.
Argument wird oktal ganzzah- int
lig ohne Vorzeichen ausgegeben.

%u

Argument wird dezimal ganzzahlig ausgegeben. Hierbei


wird es als vorzeichenlose
Zahl interpretiert.

Bedeutung

Typ des dazu


bergebenen
Arguments

Die Anzahl der auszugebenden Nachkommastellen ist anzugeben oder mit 6 festgelegt.

Typ des dazu


bergebenen
Arguments

%o

%e, %E

double
Das bergebene Argument
wird als Gleitkommazahl interpretiert. Die Ausgabe erfolgt
im Format
[-]m.ddddddexx bzw.
[-]m.ddddddExx

%g, %G

double
Das bergebene Argument
wird als Gleitkommazahl interpretiert. Falls der Exponent
kleiner als 4 ist, oder nicht
kleiner ist als die angegebene
Anzahl Nachkommastellen,
wird %E verwendet. Ansonsten wird %f verwendet.

%p

Ausgabe der bergebenen


Adresse als Zeiger.

%%

Keine Interpretation als Formatstring. Es wird das %-Zeichen ausgegeben.

unsigned int

%x

int
Eingabedaten werden hexadezimal ganzzahlig ohne Vorzeichen ausgegeben.

%c

Ausgabe eines einzelnen,


nach int konvertierten Zeichens.

char

%s

Aus der als Argument berge- char *


benen Zeichenkette werden
bis zum '\0'-Zeichen Zeichen
auf die Konsole ausgegeben,
oder so lange, wie es die angegebene Genauigkeit verlangt.

%f

double
Das bergebene Argument
wird als Gleitkommazahl interpretiert und als
[-]m.dddddd ausgegeben.

Prof. Dr. U. Matecki

Format
zeichen

Seite

25

Prof. Dr. U. Matecki

void *

Seite

26

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.3.2 Einlesen von Tastatureingaben mit


scanf()

#include <stdio.h>

hello4/hello4.c:

Zum Testen von Programmen ist es oft notwendig, Werte fr


die Berechnungen des Programms ber die Tastatur einzugeben. Dies wird in C hufig mit der Funktion scanf() bewerkstelligt. Sie ist das Gegenstck zu printf() und formatiert Texteingaben von Tastatur in z. B. int- oder float-Werte
um.

Zeichenketten werden
von scanf(...) OHNE
&-Operator eingelesen!!

int main()
{
/* Konstanten lernen wir erst spaeter kennen, daher
* ist VOLLJAEHRIG hier eine normale lokale Variable
*/
int VOLLJAEHRIG = 18;

/* ACHTUNG: Zeichenketten sind in C gewoehnliche char-Arrays */


char vorname[50];
char nachname[50];
int alter;
float tg;
printf("\nVorname: ");
Elementare Datentypen
scanf("%s", vorname);

Beispiel:

printf("\nNachname: ");
scanf("%s", nachname);

Eingabe von Vorname, Nachname, Alter und monatlich


verfgbarem Taschengeld ber die Tastatur. Das Programm
berechnet aus dem Alter, in wieviel Jahren bzw. seit wieviel
Jahren der Nutzer des Programms whlen gehen darf.
Anschlieend werden die eingegebenen bzw. berechneten
Werte mit printf() auf den Bildschirm ausgegeben.

(z. B.
int oder float) werden von
scanf(...) MIT &-Operator
eingelesen!!

printf("\nWie alt sind Sie? (ganzzahlig eingeben): ");


scanf("%d", &alter);
printf("\nWieviel Taschengeld haben Sie im Monat?:");
scanf("%f",&tg);
if(alter < VOLLJAEHRIG)
{
printf("\n\nHallo %s %s\n", vorname, nachname);
printf("Sie duerfen in %d Jahren waehlen gehen\n", VOLLJAEHRIG - alter);
}
else
{
printf("Hallo %s %s\n", vorname, nachname);
printf("Sie duerfen seit %d Jahren waehlen gehen\n", alter - VOLLJAEHRIG);
}
printf("Sie haben im Monat %6.2f Euro Taschengeld zur Verfuegung!\n",tg);
return 0;
}

Prof. Dr. U. Matecki

Seite

27

Prof. Dr. U. Matecki

Seite

28

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 scanf() nimmt Tastatureingaben entgegen und interpretiert


sie gem der im Formatstring angegebenen %-Anweisungen.
Die durch Leerzeichen oder Tabulatoren getrennten, eingegebenen Wrter werden in den hinter dem Formatstring bergebenen Variablen gespeichert.

Allgemeines Verhalten von scanf() (zum Nachschlagen fr


spter):

 Bei diesen nachfolgenden Variablen (im obigen Programm:


vorname, nachname und alter) merken wir uns vorlufig folgendes:

Werden Tastatureingaben in Variablen elementaren


Datentyps (also int, float oder hnliches) gespeichert, so
mssen diese mit vorangehendem &-Operator (Adreoperator) bergeben werden. Dies ist wegen C-spezifischen Speicherverwaltungs-Grnden unbedingt notwendig
und wird spter genauer erlutert. Vergessen wir den &Operator, so kann dies zu Programmabstrzen fhren!!!

 In der Reihenfolge, in der die Formatieranweisungen im


Formatstring stehen, werden die eingelesenen Zeichenfelder umgewandelt und in die bergebenen Variablen
geschrieben.
 Der Formatstring von scanf() hat folgendermaen
auszusehen:
o Er kann Leerzeichen oder Tabulatorzeichen enthalten, die ignoriert werden.
o Er kann gewhnliche Zeichen enthalten (nicht aber
%), die dem nchsten Zeichen auf der Konsole entsprechen mssen.
o Er kann %-Formatanweisungen enthalten. Diese
bestehen aus (in dieser Reihenfolge):
 %-Zeichen

Werden Tastatureingaben mit %s in char-Arrays eingelesen, so ist der &-Operator unbedingt wegzulassen!!
Sonst kann es ebenfalls zu Programmabstrzen kommen.

 Optional einer der Buchstaben h, l oder L, der


die Lnge der Variablen beschreibt, in die gelesen wird (h fr short, l oder L fr long).
 Optional: ein *, welcher die Zuweisung des eingelesenen Wertes an das nchste Argument
verhindert.

Die Begrndung hierfr wird nachgeliefert, sobald wir mehr


ber Speicherverwaltung gelernt haben

 Optional eine Zahl, welche die maximale Feldbreite der Eingabe (in Zeichen) festlegt.
 Die brigen Argumente von scanf() mssen alle Zeigervariablen sein (!!!!). Zeigervariablen werden spter genauer erlutert.

Prof. Dr. U. Matecki

Seite

29

Prof. Dr. U. Matecki

Seite

30

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 scanf() berliest smtliche eingegebenen Zwischenraumzeichen (Zeilentrenner, Leerzeichen, Tabulatoren,


usw.). Eine Formatieranweisung bestimmt, in was das
nchste sog. Eingabefeld (also das nchste eingegebene
Wort) umgewandelt wird.

Format Bedeutung
zeichen
von scanf()!!!
%s

 Die folgende Tabelle zeigt die bei scanf() gltigen %-Formatieranweisungen, sowie den Datentyp des Arguments
Sie wird derzeit nur der Vollstndigkeit halber angegeben
und genauer erlutert, wenn Zeiger und Speicherverwaltung behandelt wurden.
Format Bedeutung
zeichen
%d

Typ des dazu


bergebenen
Arguments

Eingabedaten werden dezimal, int*


ganzzahlig interpretiert.

Eingabedaten bis zum nchs- char *


ten Zwischenraumzeichen werden als Zeichenkette interpretiert. Das dazu bergebene
char*-Argument mu auf einen
Speicherbereich zeigen, der
gengend Platz fr die eingegebene Zeichenkette + das von
scanf() angehngte '\0'-Zeichen
enthlt.

%e,
Eingabedaten bis zum nchs- float *
%f, %g ten Zwischenraumzeichen werden als Gleitpunktzahl interpretiert. Bei der Eingabe ist optional die Angabe eines Vorzeichens, die Angabe eines Dezimalpunktes und die Angabe eines Exponenten mglich.

%i

Eingabedaten werden ganzzah- int*


lig interpretiert. Sie knnen oktal oder hex (mit 0x oder 0X am
Anfang) eingegeben werden.

%o

Eingabedaten werden oktal


ganzzahlig interpretiert (mit
oder ohne 0 am Anfang).

int*

%u

Eingabedaten werden dezimal


ganzzahlig ohne Vorzeichen
interpretiert.

unsigned int*

%x

Eingabedaten werden hexadezimal ganzzahlig interpretiert.

int *

%c

Eingabedaten werden als 1


Zeichen interpretiert.
ACHTUNG: %c funktioniert
nicht fr jede Implementierung

char *

Prof. Dr. U. Matecki

Typ des dazu


bergebenen
Arguments

Seite

31

Erkennt die Eingabe von %.


Eine Zuweisung findet nicht
statt.

Prof. Dr. U. Matecki

Seite

32

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.3.3 Selbstlern-Kapitel: Weitere Anwendung


erster Ein-/Ausgabefunktionen in C
Bisher verwendet: printf-Funktion fr Testausgaben auf den
Bildschirm

Wir knnen das Programm nun mit Hilfe der Funktion


void putchar(int c);
erweitern:

#include <stdio.h>

Neu: die Funktion


int getchar();
liefert ein von der Tastatur eingelesenes, in einen int-Datentyp
konvertiertes Zeichen zurck.

/* Ein Zeichen von Tastatur einlesen


*/
int main()
{
int c;
c = getchar();
putchar(c);
return 0;

Beispiel 1:

Diese Funktion gibt das als int-Parameter bergebene Zeichen auf den Bildschirm aus.

#include <stdio.h>
/* Ein Zeichen von Tastatur einlesen
*/
int main()
{
int c;
c = getchar();

Warum arbeiten diese Funktionen mit int-Werten?


Die Funktion getchar() muss irgendwie erkennen, wann die
Eingabe von Tastatur beendet ist. Dies geschieht ber den
Wert EOF, der als #define in der Datei <stdio.h> steht.

printf("%c eingetippt\n", (int)c);


return 0;
}

Bei diesem Wert handelt es sich um einen ganzzahligen intWert, der sich von allen druckbaren Zeichen in ASCII-Tabellen
unterscheidet.

Was passiert hier, wenn der Nutzer das Zeichen 'a' eintippt
und anschlieend die ENTER-Taste drckt?
Die Funktion getchar() nimmt dieses Zeichen entgegen und
speichert es in der Variablen c ab. Da es sich bei einem Zeichen um einen 8-Bit-Wert handelt, die Funktion aber einen (bei
uns) 32-Bit-Wert zurckliefert, werden vorher 24 binre Nullen
in die vorderen Bits des int-Wertes geschrieben.

Prof. Dr. U. Matecki

Seite

33

Prof. Dr. U. Matecki

Seite

34

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.4 Anwendung des C-Prprozessors


Prprozessor-Direktiven #include und
#define

len. . Dieser Zwischen-Quellcode wird anschlieend von gcc in


Maschinencode bersetzt.

In diesem Abschnitt wird zunchst der C-Prprozessor erlutert. Danach lernen Sie zwei der am hufigsten benutzten Prprozessor-Befehle (Prprozessor-Direktiven) genauer kennen:
#include und #define.

Die bisherige Darstellung des bersetzungs- und Bindevorganges eines C-Programmes war etwas vereinfacht. Das
folgende Bild zeigt den Vorgang etwas detaillierter:
ZwischenQuelltext

main.c:
#include <stdio.h>
int main()
{
// ein schoenes
// C-Programm
int i = rechne();
}

rechne.c:
#include <math.h>
int rechne()
{
return 3*4;
}

Ruft cpp im
Hintergrund
auf
gcc c

1.1.4.2 Der #include-Befehl:


Bisher in den Hello-World-Programmen genutzt: die #includeAnweisung. Diese sorgt dafr, da vor der eigentlichen bersetzung die hinter #include angegebene Datei vom
Prprozessor in den Zwischen-Quellcode kopiert wird.
Dahinter wird der restliche Quelltext der .c Datei kopiert. Der
so erzeugte Gesamt-Quelltext wird anschlieend bersetzt. Bei
hello1.c sieht das folgendermaen aus:

1.1.4.1 Der C-Prprozessor

C-Quelldateien:

ACHTUNG: Den Prprozessoraufruf mssen Sie nicht gesondert veranlassen; gcc erledigt diesen Arbeitsschritt automatisch.

Binre Objekt- Ausfhrbare


dateien mit
ProgrammMaschinen- datei:
code:

hello1/hello1.c:
#include <stdio.h>
int main()
{
printf("Hallo Mitglieder der LVA Programmentwicklung 2\n");

main.o:

main.c

001011...

rechne:

return 0;
}

ZwischenQuelltext

001011...
gcc o rechne main.o rechne.o

wird beim Aufruf von gcc zu

11101...

Ruft cpp im
Hintergrund
auf

rechne.o:
gcc c

Zwischen-Quelltext von hello1.c:

rechne.c

...
...
kompletter Text aus der Datei stdio.h
...
...
int main()

11101...

Beim Aufruf von gcc wird automatisch zunchst der so


genannte Prprozessor cpp aufgerufen. Dieser verarbeitet im
C-Quelltext angegebene Befehle sog. PrprozessorDirektiven, welche mit dem Zeichen # eingeleitet werden.
Einen solchen Befehl haben Sie schon kennen gelernt: Den
Befehl #include. Der Prprozessor erzeugt aus dem Quelltext
einen Zwischenquellcode mit den fertig verarbeiteten #-BefehProf. Dr. U. Matecki

Seite

35

{
printf("Hallo Mitglieder der LVA Programmentwicklung 2\n");
return 0;
}

Prof. Dr. U. Matecki

Seite

36

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.4.3 Anwendung des #define-Befehls fr


symbolische Konstanten:

die Stellen im Programm kopiert, an denen die Namen der


#defines auftauchen.

Es gibt noch weitere #-Befehle fr den C-Prprozessor, die alle


vor der eigentlichen bersetzung Auswirkungen auf den C-Zwischen-Quellcode haben. Fr uns derzeit von Bedeutung: der
Befehl #define in seiner einfachsten Anwendung. Mit ihm
kann man u. a. Konstanten definieren.
Allgemein wird eine symbolische Konstante wie folgt vereinbart:

Die drei untenstehenden printf()-Aufrufe zeigen, wie diese


Konstanten genutzt werden knnen:
Genauere Betrachtung der #define-Anweisungen:
Betrachtung der printf()-Anweisungen
...
#define HELLO "Hallo Mitglieder der LVA Programmentwicklung"
...
#define NUMMER 2

#define Name_der_Konstante Ersatztext


Beispielprogramm, hnlich wie hello3.c:
...
printf("%s %d\n\n", HELLO, NUMMER);

hello5/hello5.c:
#include <stdio.h>
#define
#define
#define
#define
#define
#define

Hier wird der


obenstehende String
als Textersatz hineinkopiert

HELLO "Hallo Mitglieder der LVA Programmentwicklung"


DAS_IST_FLOAT "Das ist ein float:"
DAS_IST_CHAR "Das ist ein char:"
NUMMER 2
FLOAT_WERT 1.74
CHAR_WERT 'a'

int main()
{
printf("%s %d\n\n", HELLO, NUMMER);

Hier wird die


obenstehende Zahl
als Textersatz
hineinkopiert

Ergebnis des Prprozessor-Aufrufes:

printf("%s %f\n\n", DAS_IST_FLOAT, FLOAT_WERT);


printf("%s %c\n\n", DAS_IST_CHAR, CHAR_WERT);
return 0;
}

Die drei oberen #defines definieren Zeichenketten-Konstanten. Die unteren drei #defines definieren eine int-Konstante,
eine float-Konstante und eine char-Konstante. Der Code
hinter dem Schlsselwort #define wird vom Prprozessor an
Prof. Dr. U. Matecki

Seite

37

Prof. Dr. U. Matecki

Seite

38

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.5 Das Werkzeug make Automatisierung der Programmgenerierung

xxx.o

gcc -c

xxx.c

1.1.5.1 Grundlagen und Vorberlegungen

yyy.o

gcc -c

yyy.c

zzz.o

gcc -c

zzz.c

myprog.o

gcc -c

myprog.c

xxx.h

yyy.h

myprog

gcc -o

In den vorherigen Programmen hello1 bis hello5 konnten


Sie die Anwendung des C-Compilers und des C-Prprozessors
sehen. Wir stellten beim Compiliervorgang fest:

zzz.h

1. Grere Programmsysteme werden in mehrere Module


aufgeteilt. Hierbei enthlt jede Quelldatei zusammengehrende Funktionalitt (vgl. Aufteilung in Java!!).
2. Die main()-Funktion wird i. d. R. in einer eigenen CQuelldatei abgespeichert.

Sie entwickeln ein Programmsystem, welches aus den


Modulen xxx.c, yyy.c, zzz.c und myprog.c samt zugehrigen Headerdateien besteht.

3. Symbolische Konstanten und Funktionsprototypen eines


Moduls werden in einer Header-Datei (Endung .h) deklariert. Diese Headerdateien werden mit dem Befehl
#include in alle Module eingebunden, in denen Funktionen des dazugehrenden Fremdmoduls aufgerufen werden.

Die Module xxx.c, yyy.c und myprog.c binden z. B.


alle die Header-Datei xxx.h ein. Wenn Sie nun die Headerdatei xxx.h ndern, d. h. editieren, sind davon die
Module xxx.c, yyy.c und myprog.c betroffen. Die
Objekt-Dateien xxx.o, yyy.o und myprog.o sind allesamt abhngig von xxx.h.

4. Jede einzelne Quelldatei (Endung .c) wird mit einem Compileraufruf in eine Objekt-Datei (Endung .o) bersetzt.

Folgendes Bild veranschaulicht alle Abhngigkeiten des


obigen Szenarios in einer Baumstruktur:

5. Die erzeugten Objekt-Dateien werden anschliessend zu einem ausfhrbaren Programm gebunden.


6. Der Gesamt-bersetzungsvorgang ist jedoch bei einem
greren Programmsystem, welches z. B. aus 50 Quelldateien besteht, umstndlich und fehleranfllig.
Warum?
Betrachten Sie folgendes Szenario:
Prof. Dr. U. Matecki

Seite

39

Prof. Dr. U. Matecki

Seite

40

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


Schritt 1: Makefile schreiben und unter dem Namen Makefile oder makefile in dem Verzeichnis abspeichern, in
dem die Quelldateien liegen.

myprog

Schritt 2: In dem Verzeichnis, in dem das Makefile abgespeichert ist, das Kommando make aufrufen.
xxx.o

yyy.o

zzz.o

myprog.o

ACHTUNG:
make findet anhand des nderungsdatums der Dateien
heraus, welche Datei die aktuellste ist. make schaut nicht
nach dem Inhalt der Dateien!!!
xxx.c

xxx.h

yyy.c

yyy.h

zzz.c

zzz.h

myprog.c

Zum Testen eines Makefiles kann das Aktualisierungsdatum von Dateien unter Linux mit
A
heit hier: A ist abhngig von B

touch Dateiname

Mit anderen Worten: Wenn eine Datei der unteren Ebene


gendert wird, so mssen alle Objektdateien, die von ihr
abhngig sind, neu erzeugt werden.

auf Kommandozeilenebene auf die aktuelle Zeit/das aktuelle Datum heraufgesetzt werden.

Das Kommandozeilenwerkzeug make hilft uns, diesen


Vorgang zu automatisieren. Hierzu werden sog. Makefiles
geschrieben, die alle notwendigen Compilieraufrufe und
alle oben gezeigten Abhngigkeiten enthalten und anschliessend mit make ausgefhrt. Arbeitsschritte (vorlufig, fr einfache Anwendungen):

Prof. Dr. U. Matecki

Seite

41

Prof. Dr. U. Matecki

Seite

42

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.5.2 Erstes Make-Beispiel (Programmverzeichnis make1):

Analyse der einzelnen Abschnitte:


1. Die Wurzel des Baumes ist das ausfhrbare Programm.
Dieses ist unmittelbar abhngig von den Objektdateien
xxx.o, yyy.o, zzz.o und myprog.o. Die Abhngigkeit wird
immer angegeben mit:

###### Unser erstes Makefile


#
###### 1. Linken der Objektdateien zum lauffaehigen Programm
myprog: xxx.o yyy.o zzz.o myprog.o
gcc -o myprog xxx.o yyy.o zzz.o myprog.o

zu_erzeugendes_ziel:

abhngigkeiten

###### 2. Compilieren der Quelldateien zu Objektdateien


xxx.o:
xxx.c xxx.h
gcc -c xxx.c

ACHTUNG: Darauf achten dass diese Zeile IMMER in der


ersten Spalte (also ohne Einrckung) stehen muss!!

yyy.o:
yyy.c yyy.h xxx.h zzz.h
gcc -c yyy.c

Unmittelbar darunter wird das Linker-Kommando, mit dem


das zu erzeugende Ziel herzustellen ist, angegeben.

zzz.o:
zzz.c zzz.h
gcc -c zzz.c

ACHTUNG: Damit "make" erkennt, dass hier auf der ShellEbene ein Kommando-Aufruf zu starten ist, wird das
Kommando mit TAB (nicht mit Leerzeichen!!!!) eingerckt.

myprog.o:
myprog.c xxx.h yyy.h zzz.h
gcc -c myprog.c
###### 3. Alte Objektdateien loeschen

Insgesamt in unserem Beispiel also:


###### 1. Linken der Objektdateien zum lauffaehigen
myprog: xxx.o yyy.o zzz.o myprog.o
gcc -o myprog xxx.o yyy.o zzz.o myprog.o

clean:
echo loesche *.o
rm -f *.o

Hierbei werden die Abhngigkeiten wie in der vorher gezeigten Baumstruktur von oben nach unten (Wurzel  Bltter)
angegeben. Kommentare werden wie in Shellscripten mit #
eingeleitet.

Prof. Dr. U. Matecki

Seite

43

Prof. Dr. U. Matecki

Seite

44

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2. Im zweiten Abschnitt sehen wir die Abhngigkeitsregeln


der einzelnen Objektdateien, wieder in der Form
zu_erzeugendes_ziel:

3. Der dritte Abschnitt enthlt noch eine Besonderheit von


make: Ein zu erzeugendes Ziel mit leerer Abhngigkeitenliste in der Form:

abhngigkeiten
zu_erzeugendes_ziel:

mit darunterstehendem, mit TAB eingercktem Compileraufruf:


Abhngigkeits
###### 2. Compilieren der Quelldateien zu
Objektdateien
regeln
xxx.o:
xxx.c xxx.h
gcc -c xxx.c

Darunter befinden sich wieder mit TAB eingerckt ein


echo-Kommando und ein rm-Kommando, welches smtliche .o-Dateien lscht (rm f):
Wozu dient dieses Ziel?

yyy.o:
yyy.c yyy.h xxx.h zzz.h
gcc -c yyy.c
zzz.o:
zzz.c zzz.h
gcc -c zzz.c

clean:
echo loesche *.o
rm -f *.o

myprog.o:
myprog.c xxx.h yyy.h zzz.h
gcc -c myprog.c

Das Werkzeug make kann auch in der Form

Compiler-Aufruf, mit
TAB eingerckt

make ziel_im_makefile
aufgerufen werden. In unserem Fall:
make clean
Ein Aufruf dieser Form bewirkt, dass die Kommandos unter dem Ziel (hier: clean) ausgefhrt werden.
Das Kommando "make clean" heisst also beim obigen
Makefile nichts anderes als "mache sauber", indem alle
alten .o-Dateien gelscht werden und vorher freundlicherweise eine echo-Ausgabe mit den Namen der zu lschenden Dateien durchgefhrt wird.

Prof. Dr. U. Matecki

Seite

45

Prof. Dr. U. Matecki

Seite

46

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.5.3 Verwendung von Makros in Makefiles


(Programmverzeichnis make2)

Mit
Makroname = Zeichenkette
wird dem Namen des Makros (hier CC und CFLAGS ) die nach
dem "=" stehende Zeichenkette zugeordnet.

###### Unser zweites Makefile: Einfuehrung von Makros


#
CC = gcc
Makrodefinition
CFLAGS = -c

Mit $(Makroname) wird auf die Zeichenkette zugegriffen, die


dem Makronamen vorher zugeordnet wurde. In unserem Fall:
Aufruf von gcc.

###### Linken der Objektdateien zum lauffaehigen Programm


myprog: xxx.o yyy.o zzz.o myprog.o
$(CC) -o myprog xxx.o yyy.o zzz.o myprog.o

Zugriff auf das Makro

Diese Form von Makefiles ist flexibler fr sptere nderungen


(z. B. anderer Compileraufruf, andere Optionen).

###### Compilieren der Quelldateien zu Objektdateien


xxx.o:
xxx.c xxx.h
$(CC) $(CFLAGS) xxx.c
yyy.o:
yyy.c yyy.h xxx.h zzz.h
$(CC) $(CFLAGS) yyy.c
zzz.o:
zzz.c zzz.h
$(CC) $(CFLAGS) zzz.c
myprog.o:
myprog.c xxx.h yyy.h zzz.h
$(CC) $(CFLAGS) myprog.c
###### Alte Objektdateien loeschen
clean:
echo loesche *.o
rm -f *.o

Prof. Dr. U. Matecki

Seite

47

Prof. Dr. U. Matecki

Seite

48

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


Allgemein knnen bei make Suffixregeln in der Form

1.1.5.4 Angabe von Suffixregeln in Makefiles


(Programmverzeichnis make3)

.von.nach:

Manche Programmgenerierungsschritte in einem groen bersetzungslauf bleiben immer gleich. So werden z. B. .o-Dateien
immer aus gleichnamigen .c-Dateien mittels eines immer gleichen Compileraufrufs generiert. Hierfr bietet make eine vereinfachte Schreibweise:

Weiterhin wurde in diesem Makefile ein Standard-Makro von


make eingefhrt:
Das Makro $< steht fr die gerade aktuell bearbeitete Quelldatei (hier: an der stelle von .von).

###### Unser drittes Makefile: Einfuehrung von Suffixregeln


#
CC = gcc
CFLAGS = -c
OBJS = xxx.o yyy.o zzz.o myprog.o
HDRS = xxx.h yyy.h zzz.h

Das Werkzeug make hat bereits einige Standard-Suffix-Regeln


bereitgestellt, die jedoch vom Entwickler berschrieben werden
knnen.

###### Linken der Objektdateien zum lauffaehigen Programm


###### Das Makro
myprog: $(OBJS)
$(CC) -o $@ $(OBJS)
######
######
######
######
.c.o:

angegeben werden. Wie bei der expliziten Angabe von Abhngigkeitsregeln wird der auszufhrende Befehl in den Zeilen darunter, mit TAB eingerckt angegeben.

Compilieren der Quelldateien zu Objektdateien wird


vereinfacht: Hier wird Abhaengigkeit von ALLEN Headerdateien angenommen. Das Makro $< steht fuer den Namen des
gerade zu uebersetzenden Quellfiles
$(CC) $(CFLAGS) $<

Suffixregel: Bei der bersetzung


einer .c-Datei in eine .o-Datei wird
der Compiler mit den angegebenen
CFLAGS aufgerufen

Name der gerade compilierten Quellxxx.o: xxx.c xxx.h


datei
yyy.o: yyy.c yyy.h xxx.h zzz.h
zzz.o: zzz.c zzz.h
myprog.o: myprog.c xxx.h yyy.h zzz.h
###### Alte Objektdateien loeschen
clean:
echo loesche *.o
rm -f *.o

Prof. Dr. U. Matecki

Seite

49

Prof. Dr. U. Matecki

Seite

50

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.1.5.5 Wichtigste voreingestellte Makros von


make

1.2 Grundlagen der Programmiersprache C


Dieses Kapitel gibt eine Einfhrung in grundlegende Sprachkonstrukte der Programmiersprache C (siehe [GOLL2005]. Es
wird vorausgesetzt, dass Konstrukte wie z. B. einfache Kontrollstrukturen mit if, while, for, usw. bereits aus Programmentwicklung 1 bekannt sind. Falls Sie hier noch ein wenig Nachholbedarf haben, schauen Sie bitte auch in das Handout Bekanntes
aus Semester 1 in neuem Gewand.

Makro

Bedeutung

$@

Name des aktuellen Ziels aus der aktuellen Abhngigkeitsregel in einem mit TAB eingerckten
Kommando.  darf nur in mit TAB eingerckten
Kommandos verwendet werden.

$$@

Name des aktuellen Ziels aus der aktuellen Abhngigkeitsregel, wenn wir uns gerade auf der
rechten Seite einer Abhngigkeitsregel befinden.
 darf nur in der rechten Seite einer Abhngigkeitsregel verwendet werden.

$*

$?

1.2.1 Variablen und Konstanten

Name des aktuellen Ziels ohne Suffix, wie z. B. .o


oder .c.
Name von Objekten auf der rechten Seite einer
Abhngigkeitsregel, die (nach Datum und Uhrzeit)
neuer sind als das zu erzeugende Ziel auf der linken Seite.
Darf nur in mit TAB eingerckten Kommandos
unterhalb einer Abhngigkeitsregel verwendet
werden.

$<

Variablen und Konstanten/symbolische Konstanten sind die


Datenobjekte, die ein Programm verarbeitet und/oder manipuliert.

1.2.1.1 Variablen
In C mssen alle Variablen vereinbart werden, bevor sie benutzt werden. Eine Vereinbarung beschreibt die Eigenschaften
von Variablen. Sie besteht aus einem (Daten- ) Typ und einer
Liste von Variablen, die diesen Typ besitzen.
Beispiel:

int wert1, wert2;


float float_wert1;

Wie $?, aber darf nur in Suffixregeln oder beim


Standard-Ziel .DEFAULT verwendet werden
(spter mehr ...).

float_wert1 = wert1 + 0.3*wert2;

Es gibt Einschrnkungen in bezug auf die Namen von Variablen


und symbolischen Konstanten. Namen bestehen aus Buchstaben und Ziffern. Das erste Zeichen eines Namens muss ein
Buchstabe sein.
Prof. Dr. U. Matecki

Seite

51

Prof. Dr. U. Matecki

Seite

52

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Der Unterstrich (z. B. _a) zhlt als Buchstabe. Dieses Zeichen


wird manchmal verwendet, um die Lesbarkeit von langen Variablennamen zu verbessern.

1.2.1.2 Konstanten und symbolische Konstanten

Beispiel:

Die Prprozessor-Direktive #define haben Sie bereits


kennengelernt. Wir haben sie bereits angewendet, um
symbolische Konstanten zu vereinbaren und wir haben
gesehen, dass diese Konstanten eigentlich nur Platzhalter
sind, die spter vom Prprozessor durch den angegebenen
Ersatztext ersetzt werden, bevor der Quelltext compiliert wird.

int dies_ist_ein_sehr_langer_name;
anstatt

Mit dem Modifizierer const kann man nun vereinbaren, da


der Wert einer einmal angelegten Variablen nicht mehr
gendert werden kann. Im Unterschied zu #define wird hier
jedoch ein echtes Datenobjekt angelegt!

int diesisteinsehrlangername;
Mindestens die ersten 31 Zeichen eines Namens sind signifikant fr den Compiler. Auf deutsch heit das:
Es gibt Compiler, die so etwas nicht mehr unterscheiden knnen (!!!):

Beispiel:
const int hilfe;

int dies_ist_ein_wirklich_sehr_langer_name;
int dies_ist_ein_wirklich_sehr_lang_gewaehlter_name;

Daher: Namen von Variablen und symbolischen Konstanten


mglichst krzer als 31 Zeichen whlen!!!

Diese Form der Konstanten wird in heutigen Quelltexten


gegenber symbolischen Konstanten, die mit #define
vereinbart werden, hufig bevorzugt.

Trotzdem sollten Variablennamen so gewhlt werden, da sie


den Verwendungszweck andeuten.

Prof. Dr. U. Matecki

Seite

53

Prof. Dr. U. Matecki

Seite

54

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die Darstellung der Werte von Konstanten sieht je nach Datentyp unterschiedlich aus. Die untenstehende Tabelle zeigt
hierbei Beispiele fr

Datentyp

Beispiel
#define DOUBLE_WERT3 4.5e+3
(steht fr 4.5 * 10+3)

 die Vereinbarung von symbolischen Konstanten mit


#define,

double dval = 0.85E-8;


(steht fr 0.85* 10-8)

 Variablen, denen ein konstanter Wert zugewiesen wird


und

float

#define FLOAT_WERT 1.55f


(endet immer mit f oder F)

char

char MY_A = a;
(Zuweisung eines einzelnen char-Wertes)

 Konstanten, die mit const vereinbart wurden.

Datentyp
int

Beispiel
#define WERT 4711
(Basis 10, beginnt immer mit einer Ziffer
ungleich 0.)

#define EOL \n
(fr Zeilentrenner )
#define EOS\0
(fr Stringende)

#define Wert 0x80FF


(Basis 16, beginnt immer mit 0x)

long

#define VTAB \013


(Oktalwert fr Vertikal-Tabulator)

#define Wert 030007


(Basis 8, beginnt immer mit 0)
#define WERT 12345L
#define WERT2 12345l

#define VTAB \xb


(Hex-Wert fr Vertikal-Tabulator)
char[]
#define HALLO HALLO KST2
oder char* (eine Zeichenkette als symbolische
Konstante)

(gefolgt von groem oder kleinem L)


unsigned
long

#define WERT 471112UL


const int WERT2 = 47111ul;

char *x = Guten Tag\n;


(Eine Zeichenkette als Zeigervariable; spter
mehr dazu)

(gefolgt von UL gro oder klein geschrieben)


double

#define DOUBLE_WERT 0.67


const char *str = piep piep\a;
(Eine Zeichenkette als Zeigerkonstante; spter
mehr zu Zeigern)

#define DOUBLE_WERT2 .67


(steht fr 0.67)
Prof. Dr. U. Matecki

Seite

55

Prof. Dr. U. Matecki

Seite

56

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Es gibt fr einige konstante char-Werte Ersatzdarstellungen,


die auf dem Papier so aussehen, als ob sie aus mehreren Zeichen bestehen wrden. In Wirklichkeit handelt es sich hierbei
jedoch jeweils um einen 8-Bit-char!!

Konstante Werte knnen jedoch auch direkt im Programmcode


verwendet werden (unsauber!! Das tun wir in unseren Praktikumsaufgaben niemals!!):

Die folgende Tabelle zeigt alle in C mglichen Ersatzdarstellungen von char-Konstanten:

\a

Klingelzeichen

\b

Backspace

\f

Mglichkeit 1: Verwendung symbolischer Konstanten:

Seitenvorschub

#define MAXVALUE 5000

\n

Zeilentrenner

\r

Wagenrcklauf

\t

Tabulatorzeichen

\v

Vertikal-Tabulator

\\

Backslash

\?

Fragezeichen

Einfaches Anfhrungszeichen

Doppel-Anfhrungszeichen

\000

oktaler Wert aus der ASCII-Tabelle

\xhh

Hexadezimaler Wert aus der ASCII-Tabelle

Prof. Dr. U. Matecki

if (i < 5000)
{
i++;
}
Besser ist es, eine spter bentigte Konstante einmal zu definieren und spter im Code nur noch den Namen der Konstanten zu verwenden:

weiterer Code
if (i < MAXVALUE)
{
i++;
}
Mglichkeit 2: Verwendung einer Konstantenvereinbarung mit
const:
const int MAXVALUE = 5000;
weiterer Code

Seite

if (i < MAXVALUE)
{
i++;
}

57

Prof. Dr. U. Matecki

Seite

58

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.2 Datentypen
Die Datentypen der Programmiersprache C knnen generell in
folgende Bereiche aufgeteilt werden:
Datentypen

ganzzahlige
Datentypen

GleitkommaDatentypen

float double

int unsigned
int

long unsigned
long

 char und unsigned char belegen in C genau 1 Byte.


(Zur Erinnerung: In Java belegt ein Zeichen 2 Byte.)

aggregierte
Datentypen

elementare Datentypen

Struktur
(struct)

Array

void

Unionen
(union)

Aufzhlungen
(enum)

Zeiger
(Pointer)

Die grau hinterlegten Datentypen sind dabei in Java nicht verfgbar.

1.2.2.1 Spezifische Eigenschaften einiger elementarer Datentypen in C


Einige elementare Datentypen in C unterscheiden sich von denjenigen in Java hinsichtlich Interpretation und Speicherplatzbedarf:
 Java kennt keine unsigned Datentypen. C dagegen unterscheidet bei den ganzzahligen Datentypen zwischen
vorzeichenbehafteten (=signed) und vorzeichenlosen
(unsigned) Datentypen. Vorzeichenbehaftet bedeutet:
Sie haben z. B. beim 2 Byte groen Datentyp short einen
Wertebereich, der sowohl negative, als auch positive WerProf. Dr. U. Matecki

 ACHTUNG: In C gibt es keinen Datentyp byte !!!!


Statt dessen wird in C-Programmen char oder unsigned
char verwendet.
 char: -128 bis 127
 unsigned char: 0 bis 255

long
double

short unsigned char unsigned


char
short

te umfasst, also z. B. -32768 bis 32767. Ein unsigned


short in C dagegen kennt keine negativen Zahlen. Statt
dessen umfasst der ebenfalls 2 Byte groe unsigned
short einen doppelt so groen positiven Wertebereich: 0
bis 65535.

Seite

59

 int und unsigned int sind ganzzahlige Werte, deren


Speicherplatzbedarf i. d. R. der Wortbreite des Rechners
entspricht (Heute meist 4 Byte).
 int: -2147483648 bis 2147483647
 unsigned int: 0 bis 4294967295
 long und unsigned long (Heute oft 8 Byte; auf
unserem System 4 Byte): Ganzzahlige Werte, die einen
greren Wertebereich bentigen als int bzw. unsigned
int.
 short und unsigned short (Heute meist 2 Byte):
Ganzzahlige Werte fr kleinere Wertebereiche
 short: -32768 bis 32767
 unsigned short: 0 bis 65535

Prof. Dr. U. Matecki

Seite

60

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 float, double, long double:


o Zur Genauigkeit von Gleitkommazahlen vgl. Wiederholungsskript S. 6
o Typische Gren von Mantisse und Exponent der
verschiedenen Gleitkomma-Datentypen in C:
Bits in
n/m/x
float
double
long double

o Array: Ein Vektor aus gleichartigen Datenelementen,


hnlich wie in Java

kleinster mgl. grter mgl.


Wert
Wert

32 / 23 / 8
3.4 * 10 38
64 / 52 / 11 1.7 * 10 308
80 / 64 / 15 3.4 * 10 4932

 Aggregierte Datentypen: Aus elementaren Datentypen zusammengesetzte, kompliziertere Datentypen. Die in C verfgbaren, zusammengesetzten Datentypen werden in
spteren Kapiteln ausfhrlich behandelt. Hier eine Kurzzusammenfassung der wichtigsten aggregierten Datentypen:

3.4 * 10 +38
1.7 * 10 +308
3.4 * 10 +4932

 In C gibt es sog. Zeiger (Pointer). Hierbei handelt es sich


um Adressen von Variablen. Zeiger werden in spteren
Kapiteln ausfhrlich behandelt.

o Struktur (struct): Zusammenfassung verschiedener


Datenattribute in einem Gesamtdatentyp. Entspricht
in etwa einer Klasse in Java. Enthlt aber nur Attribute und keine Methoden.
Beispiel: Ein Datentyp "Personal" knnte die Attribute
- Vorname (Zeichenkette)
- Nachname (Zeichenkette)
- Personalnummer (int)
- Monatsgehalt (float)
enthalten
o Aufzhlungstyp (enum): Enthlt alternative, ganzzahlige Auswahlwerte.
Beispiel: Eine Variable des Aufzhlungs-Datentyps
"Farbe" knnte z. B. entweder den Wert Rot (0), oder
den Wert Grn (2) oder den Wert Blau (3) annehmen.
o Auswahldatentyp (union): Enthlt alternative Auswahltypen.
Beispiel: Ein Auswahl-Datentyp "Angestellter" knnte
entweder den Strukturdatentyp "Boss" oder den
Strukturdatentyp "Normaler Angestellter" annehmen.

Prof. Dr. U. Matecki

Seite

61

Prof. Dr. U. Matecki

Seite

62

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.2.2 Gre von Datentypen in C

1.2.3 Vereinbarungen und Gltigkeitsbereiche

Die Gre von Datentypen aller Art auf einer Maschine kann
mit dem sog. sizeof()-Operator festgestellt werden. Er liefert
die Gre einer Variablen oder eines Datentypen (in Bytes)
zurck.
Beispiel:

Vereinbarungen beinhalten 2 Vorgnge innerhalb eines C-Programms, die folgendermaen unterschieden werden knnen:
1. Die Deklaration einer Variablen legt den Namen, den Typ
und die Speicherungsklasse einer Variablen fest.
Der Gltigkeitsbereich sagt aus, in welchem Bereich des
Quelltextes eine Variable gltig ist und in welchem Speicherbereich des kompilierten Programms sie angelegt
werden soll.

int size;
size = sizeof(int);
Dieser Code ist quivalent zu

C unterscheidet folgende Gltigkeitsbereiche:

int size;
size = sizeof (size);

auto
static
extern
register

 In beiden Beispielen enthlt size nach dem Aufruf von


sizeof() auf unseren Systemen den Wert 4, da int 4
Bytes umfat.

Der Gltigkeitsbereich auto ist die Standard-Behandlung


von lokalen Variablen in {}-Blcken. Sie werden bei jedem Funktionsaufruf neu angelegt. Der Sprachumfang von
C erlaubt das Voranstellen des Modifizierers auto vor lokalen Variablen, erzwingt es jedoch nicht. Per Konvention
wird das Schlsselwort i. d. R. weggelassen.
Der Gltigkeitsbereich extern ist fr globale Variablen,
die auerhalb jeder Funktion angelegt werden gedacht.
Der Modifizierer extern wird jedoch nur dann vor eine
globale Variable gesetzt, wenn diese schon in einer
anderen C-Datei (also extern) angelegt wurde. Er dient
dann als Querverweis auf eine andere C-Datei.

Prof. Dr. U. Matecki

Seite

63

Prof. Dr. U. Matecki

Seite

64

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Bei static-Variablen werden in C 2 Flle unterschieden:


Lokale static Variablen behalten ihre jeweils angenommenen Werte zwischen den Funktionsaufrufen bei.

modify_local(); /*Erster Aufruf: local_counter = 0 */


modify_local(); /*Zweiter Aufruf: local_counter = 0 */
modify_local(); /*Dritter Aufruf: local_counter = 0 */
modify_global(); /*Erster Aufruf: ext_counter = 0 */
modify_global(); /*Zweiter Aufruf: ext_counter = 1 */
modify_global(); /*Dritter Aufruf: ext_counter = 2 */

Globale static-Variablen sind nur in der Datei, in der sie


angelegt wurden, bekannt. Auf sie kann also kein externQuerverweis gesetzt werden.

return 0;
}

Der Gltigkeitsbereich register sagt, da Variablen


(sofern es die Rechner-Hardware erlaubt) in CPURegistern gehalten werden. Dies fhrt zu einer schnelleren
Programmausfhrung.
Programmbeispiel:
vereinbarung2/vereinbarung2.c:
int ext_counter=0;
void modify_static()
{
static int stat_counter=0;
stat_counter ++;

/* Wert beibehalten

*/

}
void modify_local()
{
int local_counter=0;
local_counter++;
}

/* jedesmal neu anlegen */

void modify_global()
{
ext_counter ++;
}
int main()
{
/* Bei Eintritt in die Funktionen gilt: */
modify_static(); /*Erster Aufruf: stat_counter = 0 */
modify_static(); /*Zweiter Aufruf: stat_counter = 1 */
modify_static(); /*Dritter Aufruf: stat_counter = 2 */

Prof. Dr. U. Matecki

Seite

65

Prof. Dr. U. Matecki

Seite

66

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2. Eine Definition einer Variablen oder einer Funktion tut


noch mehr: Sie legt die Variable oder die Funktion
auerdem im Hauptspeicher an. Die Definition z. B. einer
Funktion stellt hingegen auch Speicherplatz (bei
Funktionen: fr den Funktionscode) zur Verfgung. Die
obigen Beispiele von Deklarationen waren auch
gleichzeitig Definitionen, denn sie haben die Variablen
gleichzeitig angelegt.
Das nchste Beispiel zeigt, wie das Schlsselwort extern
zur Deklaration globaler Variablen verwendet wird, die in
einem anderen Quellcode definiert wurden.

Die globale Variable ext_zaehler wird in der Quelldatei


vereinbarung3.c definiert (d. h. angelegt).
Damit die selbstgeschriebene Funktion print_extern()
in der Quelldatei print_extern3.c sie jedoch erkennen
und verwerten kann, mu ihr mitgeteilt werden, da in einer anderen Quelldatei eine globale Variable namens
ext_zaehler angelegt wurde. Dies geschieht mit dem
Schlsselwort extern. Im obigen Fall wurde diese Mitteilung global gemacht, so dass in print_extern3.c alle
Funktionen die Variable erkennen und verwerten knnen.

vereinbarung3/vereinbarung3.c:

#include "print_extern3.h"

Definition

int ext_zaehler=0;
int main()
{
print_extern(); /*Erster Aufruf: ext_zaehler = 0 */
print_extern(); /*Zweiter Aufruf: ext_zaehler = 1 */
print_extern(); /*Dritter Aufruf: ext_zaehler = 2 */
}
vereinbarung3/print_extern3.h:

#include <stdio.h>
void print_extern();

Deklaration

vereinbarung3/print_extern3.c:

#include <stdio.h>
extern int ext_zaehler;
void print_extern()
{
printf("ext_zaehler = %d\n", ext_zaehler);
ext_zaehler = ext_zaehler + 1;
}

Prof. Dr. U. Matecki

Seite

67

Prof. Dr. U. Matecki

Seite

68

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.4 Operatoren

1.2.4.1 Grundlagen der Typumwandlung bei


der Verwendung von Operatoren

Viele Operatoren der Programmiersprache C sind bereits aus


Java bekannt und funktionieren auch so wie in Java. Diese
werden im Wiederholungs-Skript ab S. 7 noch einmal erlutert.
Hierzu gehren:
1. Arithmetische Operatoren

Hat ein arithmetischer oder logischer Operator Operanden verschiedenen Typs, so werden die Werte der Operanden nach
bestimmten Regeln umgewandelt:
Implizite Umwandlung bei arithmetischen und logischen
Operationen:

2. Vergleiche und logische Verknpfungen


3. Inkrement- und Dekrement-Operatoren
4. Ausdrcke und Zuweisungen
5. Die Bit-Manipulations-Operatoren >>, <<, &, |, ~, ^

Prof. Dr. U. Matecki

Seite

Bei der Verknpfung zweier Variablen


oder Konstanten verschiedenen Typs
gilt hier:
Schmalerer Typ wird in breiteren
umgewandelt, ohne Information zu verlieren. Das Resultat entspricht ggf.
dem hheren Typ.
double und float double
float und int  float
int und long  long
int und short  int
int und char  int
unsigned und nicht unsigned 
unsigned

69

Vektorindizes

nur ganzzahlige Datentypen (int,


short, long)

Zuweisung eines
breiteren Typs an einen schmaleren Typ

erlaubt, aber ruft ggf. Warning hervor.

char in arithmetischen oder logischen


Ausdrcken

Da char eine kleine ganze Zahl ist:


mglich! (Ersatz fr Datentyp byte in
Java)

Prof. Dr. U. Matecki

Seite

70

Handout Programmentwicklung II -- C und C++


Vorzeichen bei charWerten

Handout Programmentwicklung II -- C und C++

Kann bei Umwandlung von char 


int eine negative Zahl entstehen?
Maschinenabhngig!

Falls wir in k den Wert einer reellwertigen Division haben


wollen, tun wir folgendes:
int i = 7; j = 8;
float k;
k = (float)i / j;

Zeichen aus dem druckbaren Standard-Zeichensatz sind jedoch immer


positiv.
Explizites Umwandeln (Casting)

wandelt den Typ eines Operanden in


einen anderen, gewnschten Typ

Typumwandlung bei
Parameterbergabe
an Funktionen

spter!!

Bei Divisionen ist noch folgendes zu beachten:


Sind beide Operanden einer Division von ganzzahligem
Datentyp, so ist auch das Ergebnis ganzzahlig. Dabei werden
die Nachkommastellen immer abgeschnitten.
Beispiel:
int i = 7, j = 8, k;
k = i / j;
Die Variable k enthlt anschlieend den Wert 0. Dies gilt
brigens auch, wenn k eine Gleitkommazahl ist:
int i = 7, j = 8;
float k;
k = i / j;
Auch hier enthlt k anschlieend den Wert 0.

Prof. Dr. U. Matecki

Seite

71

Prof. Dr. U. Matecki

Seite

72

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.4.2 Bit-Manipulationen in C

Beispiel 1: Bitweise logische Operationen:

Die Sprache C stellt folgende Operatoren fr die bitweise, logische Verknpfung von ganzzahligen Werten zur Verfgung:
~
&
|
^

bitweise logische Negation


bitweises logisches AND
bitweises logisches OR
bitweises logisches XOR

bitmanip1/bitmanip1.c:
#include <stdio.h>
/* bitweise logische Verknuepfungen */
int main()
{
char a=7;
char b=5;
char c, d, e , f;

In schnellen Algorithmen werden manchmal Operatoren bentigt, die die Bits einer Zahl nach links oder nach rechts verschieben:

/* bitweises AND */
c = a & b;
/* bitweises OR */
d = a | b;

118 = 01110110  00111011 = 59 (1 Bit Rechts-Shift)


118 = 01110110  11101100 = 236 (1 Bit Links-Shift)

/* bitweises XOR */
e = a ^ b;
/* bitweises NOT */
f = ~b;

Dies kann mit folgenden Operatoren bewerkstelligt werden:


Zahl >> n_bits
Zahl << n_bits

printf("a = %x\nb = %x\n\n",a,b);


printf("c = %x\nc = %x\ne=%x\ne=%x\n",c,d,e,f);

Rechts-Shift um n_bits
Links-Shift um n_bits

return 0;

Bei der Behandlung von signed und unsigned-Variablen (char,


short, int, long) ist folgender Unterschied zu beachten:

Bei Rechts-Shift einer unsigned-Variablen werden die oberen


Bits mit Nullen aufgefllt, bei vorzeichenbehafteten Variablen
mit dem alten obersten Bit.

Prof. Dr. U. Matecki

Seite

73

Prof. Dr. U. Matecki

Seite

74

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel 2: shift mit unsigned-Variablen:

Beispiel 3 :kompliziertere shifts:

bitmanip2/bitmanip2.c:

bitmanip3/bitmanip3.c:

#include <stdio.h>

#include <stdio.h>

/* Bitshift-Operationen: Erster Versuch */


int main()
{
unsigned char a=118;

/* Bitshift-Operationen: Zweiter Versuch mit signed


* und unsigned-Operanden
*/
int main()
{
unsigned char a=140;
char b=-116;
char c= 116;

unsigned char c;
unsigned char d;

unsigned char d;
char e;
char f;

/* Um 1 Bit nach rechts verschieben */


c = a >> 1;

/* unsigned char um 2 Bit nach rechts verschieben:


* Linke Seite wird mit Nullen aufgefuellt
*/
d = a >> 2;

/* Um 1 Bit nach links verschieben */


d = a << 1;
printf("a = %d\n\n", a);
printf("c = %d\nd = %d\n", c, d);

/* Negativen char-Wert um 2 Bit nach rechts verschieben:


* Linke Seite wird mit 1sen aufgefuellt, da das linke
* (Vorzeichen-)Bit bei negativen, vorzeichenbehafteten
* Zahlen 1 ist
*/
e = b >> 2;

return 0;
}

/* Positiven char-Wert um 2 Bit nach rechts verschieben:


* Linke Seite wird mit 0sen aufgefuellt, da das linke
* (Vorzeichen-)Bit bei positiven, vorzeichenbehafteten
* Zahlen 0 ist
*/
f = c >> 2;
printf("a = %d\nb = %d\nc = %d\n\n", a, b, c);
printf("d = %d\ne = %d\nf = %d\n\n", d, e, f);
return 0;
}

Prof. Dr. U. Matecki

Seite

75

Prof. Dr. U. Matecki

Seite

76

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Was geschieht hier?


unsigned:

14010 = 100011002,
14010 >>2 = 3510 = 001000112

signed:

-11610 = 100011002
-11610 >> 2 = -2910 = 111000112

signed:

11610 = 011101002
11610 >> 2 = 2910 = 000111012

1.2.4.3 Vorrang bei der Anwendung von Operatoren in C


Die folgende Tabelle zeigt alle in C verfgbaren Operatoren von
oben nach unten angeordnet. (Hhere Zeile == Hherer Rang!)
Operatoren mit gleicher Priorittsnummer haben gleiche Rangfolge.
Die Assoziativitt sagt aus, ob die Operatoren von links oder
von rechts her zusammengefasst werden.
Achtung: Adress-Operatoren und Zeiger sind hier noch nicht
bekannt. Sie werden aber der Vollstndigkeit halber ebenfalls in
dieser Tabelle aufgefhrt.

Prof. Dr. U. Matecki

Seite

77

Prioritt Operator

Bedeutung

1
1
1
2
2
2

()
-> und .
[]
++ -+ sizeof

Funktionsaufruf
Memberzugriff bei Strukturen
Arrayindex
Inkrement, Dekrement
Vorzeichen (unr)
Gre von Variablen und Datentypen

Assoziativitt
von links
von links
von links
von rechts
von rechts
von rechts

2
2
2
3
3
3
4
5
6

(Datentyp)
&
*
*
/
%
+ << >>
< <=

Typecast
Adressoperator
Dereferenzierung von Zeigern
Multiplikation
Division
modulo
Summe, Differenz (binr)
Bitshift
kleiner, kleiner gleich

von rechts
von rechts
von rechts
von links
von links
von links
von links
von links
von links

> >=

grer, grer gleich

von links

== !=

gleich, ungleich

von links

Prof. Dr. U. Matecki

Seite

78

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Prioritt Operator

Bedeutung

Assoziativitt

8
9
10
11
12
13
14

&
^
|
&&
||
?:
= += -=
>>= <<=
(und alle
anderen
Zuweisungsoperatoren)

Bitweises UNDD
Bitweises Exklusiv-ODER
Bitweises ODER
Logisches UND
Logisches ODER
bedingte Auswertung
Zuweisung, kombinierte Zuweisungsoperatoren

von links
von links
von links
von links
von links
von rechts
von rechts

Komma-Operator

15

1.2.5 Selbstlern-Kapitel: Blcke und


Kontrollstrukturen
1.2.5.1 Einfhrung
Wie in Java knnen in C mehrere Anweisungen mit geschweiften Klammern zu einer Anweisung zusammengefat werden.
Mit den folgenden Kontrollstrukturen knnen auf diese Weise
ganze Programmabschnitte bedingt oder wiederholt ausgefhrt
werden.
Folgende Kontrollstrukturen knnen bei Bedarf im Wiederholungsskript ab S. 25 nachgelesen werden, da sie im Java-Teil
der Vorlesung schon behandelt wurden:

von links

1. Auswahl mit if-Anweisung


2. Zhlschleife mit for
3. Abweisende Schleife mit while
4. Nicht-abweisende Schleife mit do while
5. Unterbrechung des Kontrollflusses mit break und
continue
6. Fallunterscheidung mit switch case
Die Programmbeispiele hierzu wurden in der Sprache C verfasst und geben Zwischenergebnisse mit printf() aus. Im
folgenden werden noch 2 zustzliche Kontrollstrukturen eingefhrt, die Sie in Java nicht kennen:

Prof. Dr. U. Matecki

Seite

79

Prof. Dr. U. Matecki

Seite

80

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.5.2 exit-Funktion

1.2.5.3 goto-Anweisung 

Falls unter einer abgefragten Abbruchbedingung nicht nur z. B.


eine Schleife oder ein case-Zweig einer switch-Anweisung verlassen werden soll (break-Anweisung), sondern das gesamte
Programm abgebrochen werden soll, so wird die exit-Anweisung verwendet:

Die goto-Anweisung ermglicht den Sprung an eine vorher definierte "Textmarke" im Programmcode. Sie kann zur Erzeugung beliebig unlesbarer Programme mibraucht werden
("Spaghetti-Code"), wird jedoch manchmal aus PerformanceGrnden verwendet (aber nicht von uns!!!).
Beispiel 1:

exit1/exit1.c
#include <stdio.h>

goto1/goto1.c

/* Zeichen einlesen und verschiedene Zeichen zaehlen


*
*/
int main()
{
int c;
int anzahl_leerzeichen=0;
int anzahl_a = 0;

#include <stdio.h>
const int MAX_AUSSEN = 5;
const int MAX_INNEN = 7;
/* goto-Spruenge: Verlassen von 2 Schleifen mit per Sprung
* nach Ausgabe eines Dreiecks
*/
int main()
{

while ((c = getchar()) != EOF)


{
switch (c)
{
case 'a': anzahl_a ++;
break;
case 'b': printf("\nbye!\n");
exit(0);

int i, j;
for (i=0; i<MAX_AUSSEN; i++)
{
for (j=0; j<MAX_INNEN; j++)
{
printf("x ");
if (j >= i)
goto zeilenende;
}
zeilenende:
printf("\n");
if(i == MAX_INNEN)
goto ende;

case ' ':


case '\t':
case '\n': anzahl_leerzeichen ++;
break;
default:

putchar(c);
break;
} /*Ende switch */

ende:
}

}
return 0;

} /*Ende while */
printf("Anzahl a: %d\n", anzahl_a);
printf("Anzahl Leerzeichen: %d\n", anzahl_leerzeichen);
return 0;
}

Prof. Dr. U. Matecki

Seite

81

Prof. Dr. U. Matecki

Seite

82

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Was tut das Programm:

Das folgende Programm ohne_goto1.c tut das gleiche wie


goto1.c, nur ohne goto-Anweisung:

 Ausgabe eines Dreiecks mit x-en


ohne_goto1/ohne_goto1.c

 uere Schleife: luft mit Laufindex i laut Schleifenkopf


von 0 bis MAX_AUSSEN

#include <stdio.h>

 Innere Schleife: luft mit Laufindex j laut Schleifenkopf


von 0 bis MAX_INNEN
 In der inneren Schleife wird bei jedem Schleifendurchlauf
ein x gefolgt von einem Leerzeichen ausgegeben.

const int MAX_AUSSEN = 5;


const int MAX_INNEN = 7;
/* tut das gleiche wie goto1, aber ohne goto */
int main()
{
int i, j;
for (i=0; i<MAX_AUSSEN; i++)
{
for (j=0; j<MAX_INNEN; j++)
{
printf("x ");
if (j >= i)
break; /* Verlassen der inneren Schleife */
}
printf("\n");
if(i == MAX_INNEN)
break; /*Verlassen der aeusseren Schleife*/

 Wenn innerer Schleifenindex j den ueren Index i berschreitet, wird die innere Schleife mit goto zeilenende
verlassen.
 An der Marke zeilenende wird ein Zeilentrenner \n ausgegeben. Wenn bei dieser Marke der uere Schleifenindex i die Konstante MAX_INNEN berschreitet, so wird
auch die uere Schleife mit dem Sprung goto ende verlassen.

}
return 0;
}

Unbedingt beachten: Nach einem goto-Sprung, z. B. zur Marke zeilenende wird der darunterstehende Code weiter ausgefhrt (fall-through, hnlich wie case ohne break in einer
switch-Anweisung). Falls dies nicht gewnscht ist: ein weiterfhrendes goto einfhren, z. B. zur Marke ende.
Daher sind Programme mit goto-Anweisungen so ungemein
"lesbar".
In unseren Praktika sind goto-Anweisungen verboten, es
sei denn, in der Aufgabenstellung steht, da sie erwnscht
sind!!
Prof. Dr. U. Matecki

Seite

83

Prof. Dr. U. Matecki

Seite

84

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

1.2.6 Funktionen und Programmstruktur

Im Beispiel hello.java:

1.2.6.1 Einleitung

 Klassenname: Hello2

C ist keine objektorientierte Programmiersprache wie Java,


sondern eine strukturierte Sprache.
Einfhrendes Beispiel: Programmstruktur eines Java-Programms vs. Programmstruktur eines C-Programms:

 Methoden: main() und print_hello2()


Die Methode main() ruft die Methode print_hello2() auf,
welche eine Ausgabe auf den Bildschirm durchfhrt.

java/hello2/Hello2.java
public class Hello2 {
public static void print_hello2 () {
System.out.println("Hallo Kurs Programmentwicklung 2");
}
public static void main(String[] args) {
print_hello2();
}
}

// end main

// end class Hello2

In Java besteht jedes Programm aus mindestens einer Klasse.


Eine der Klassen enthlt die sog. main()-Methode, die die Ablaufsteuerung des Programms enthlt und ggf. weitere Methoden.
In der main()-Methode werden i. d. R weitere Methoden der
Klasse oder Methoden anderer Klassen aufgerufen, die zusammengehrende Unteraufgaben erledigen.
Jede Methode kann weitere Methoden der eigenen Klasse und
Methoden anderer Klassen aufrufen.

Prof. Dr. U. Matecki

Seite

85

Prof. Dr. U. Matecki

Seite

86

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

In C gibt es dagegen keine Klassen. Wollen wir hier eine sinnvolle Unterteilung eines Programms in Unteraufgaben vornehmen, so tun wir dies mit Hilfe von Funktionen. Das obige JavaProgramm wird in C in etwa folgendermaen aussehen:

hello2/hello2Funcs.h:

void hello2();

Deklaration des Funktionsprototypen unserer


hello2-Funktion.
Auf diese Weise wissen aufrufende Funktionen,
wie sie hello2 zu nutzen haben. (Parameter und
Rckgabewerte)

hello2/hello2Funcs.c:
#include <stdio.h>

Info zur Standard-Ein/Ausgabe-Bibliothek einfgen

void hello2()

Funktion namens hello2 definieren, die keine Argumentwerte empfngt keinen Wert zurckgibt

Die Anweisungen der Funktion hello2 stehen in


geschweiften Klammern
hello2 ruft die Bibliotheksfunktion printf auf, um
diese Zeichenfolge zu drucken. \n stellt den
Zeilentrenner dar.
printf("Hallo Mitglieder der LVA Programmentwicklung 2\n");

 Sie werden verwendet fr

hello2/hello2.c:
#include "hello2Funcs.h"

Info zu unserer hello2 - Funktion einfgen

int main()

Funktion namens main definieren, die keine Argumentwerte empfngt und einen int-Wert zurckgibt
Die Anweisungen der Funktion main stehen in
geschweiften Klammern

hello2();

return 0;

Weiterhin besteht es aus einer hello2()-Funktion, die die


Ausgabe der "Hello"-Zeichenkette auf den Bildschirm erledigt.
 Merke: In C gehren Funktionen niemals zu einer Klasse,
da es in C keine Klassen gibt. In C stehen Funktionen als
Unteraufgaben eines Programms "fr sich allein".

Das Programm besteht aus einer main()-Funktion, die die


gleiche Aufgabe hat wie die main()-Methode in Java.

o die Unterteilung eines Programms in sinnvolle Teilabschnitte


o immer wiederkehrende Aufgaben

main ruft die von uns definierte Funktion hello2


auf.
main gibt den int-Wert 0 zurck

 Funktionen knnen selbst wieder andere Funktionen (oder


sich selbst) aufrufen.
 Die main()-Funktion in C sollte so wenig Zeilen wie
irgend mglich enthalten!
 Viele kleine Funktionen sind i. d. R. besser als wenige
groe!
 Die wichtigsten Aufgaben sind in C bereits als Funktionen
in der Standard-Bibliothek enthalten (z. B. printf(),
putchar(), getchar())

Prof. Dr. U. Matecki

Seite

87

Prof. Dr. U. Matecki

Seite

88

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Funktionen knnen zu verarbeitende Werte als bergabeparameter erhalten und sie knnen Werte an die aufrufende Funktion zurckgeben:

void main()
{
int x=5, y=7, z;
int plus (int a, int b )
{
int c;
c = a + b;
return c;
}

1.2.6.2 Deklaration und Struktur von Funktionen


Die allgemeine Struktur von Funktionsdefinitionen in C sieht
folgendermaen aus:

z = plus(x, y);

Typ_Rueckgabewert Funktionsname (typ_para1 name_para1, )


{

Vereinbarungen und Anweisungen


}

Dabei ist
 Typ_Rueckgabewert der Datentyp des Wertes, den die
Funktion zurckgibt. Hat die Funktion nur eine Unteraufgabe zu erledigen, ohne einen Wert zurckzugeben (z. B.
putchar()), so wird void als Datentyp des Rckgabewertes angegeben.
 Funktionsname ist der Name, mit dem die Funktion
spter aufgerufen wird. Konventionen fr Funktionsnamen:
siehe Programmierrichtlinien auf majestix!
 In den runden Klammern () werden evtl. zu verarbeitende
Parameter deklariert. Hierbei ist typ_para1 der Datentyp
des ersten zu verarbeitenden Parameters, name_para1
ist der Variablenname dieses Parameters. Werden mehrere Parameter bergeben, so werden sie durch Kommata
getrennt.
Falls keine Parameter bergeben werden, bleiben die runden Klammern leer.

Prof. Dr. U. Matecki

Seite

89

Prof. Dr. U. Matecki

Seite

90

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Einfachstes Beispiel fr eine Funktion

Man kann expression mit runden Klammern ( ) umgeben; Dies ist jedoch optional.

void tueNix()
{

Der zurck gelieferte Wert kann von der aufrufenden Funktion auch ignoriert werden.

}
Beispiel:
Diese Funktion tut nichts. Sie liefert auch nichts zurck, da
der Datentyp ihres Rckgabewertes mit void vereinbart
wurde.
 Einfachstes Beispiel fr eine Funktion mit Wertrckgabe:
int tueNixMitReturn()
{
return 1;
}

int tueNixMitReturn()
{
return 1.85;
}
int main()
{
float testwert;
testwert = tueNixMitReturn();
return 0;
}

int main()
{
int testwert;
testwert = tueNixMitReturn();
}

Welchen Wert hat die Variable testwert in der Funktion


main() nach Aufruf von tueNixMitReturn()?

Der Datentyp des Wertes, der an die aufrufende Funktion


zurckgeliefert, ist int.
Die return-Anweisung liefert den Rckgabewert an die
aufrufende Funktion zurck (hier main()) und verlt die
aufgerufene Funktion (hier: tueNixMitReturn()).
Nach dem Schlsselwort return kann ein beliebiger Ausdruck folgen:
return expression;

 Lsung: 1.0. Warum?

Falls ntig, wird expression automatisch in den Resultattyp der Funktion umgewandelt.

Prof. Dr. U. Matecki

Seite

91

Prof. Dr. U. Matecki

Seite

92

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Wenn in einer Funktion func1 eine zweite Funktion


func2() aufgerufen wird, bevor sie definiert wurde, so
mu ihr Prototyp vorher deklariert werden, da sie dem
Compiler beim bersetzen sonst unbekannt ist.

Warum ist das notwendig?


Der Compiler arbeitet "von oben nach unten", d. h. er kann
nur Funktionsaufrufe bersetzen, von denen ihm vorher
mitgeteilt wurde, wie Parameterliste und Rckgabewert
auszusehen haben.

void main()
{
int x=5, y=7, z;

Nchster Schritt: Fr eine saubere Programmarchitektur


wird der Quellcode folgendermaen von uns aufgeteilt:

z = plus(x, y);
}
int plus (int a, int b )
{
int c;
c = a + b;
return c;
}

#include modul1.h
programmname.c

In einem einfachen Programm kann das folgendermaen


aussehen:
modul1.c

int plus (int a, int b);


void main()
{
int x=5, y=7, z;

int main ()
{
}

int plus (int a, int b)


{
...
}

#ifndef MODUL1_H
#define MODUL1_H
modul1.h

z = plus(x, y);
}

int plus (int a, int b);

int plus (int a, int b )


{
int c;
c = a + b;
return c;
}

#endif

Prof. Dr. U. Matecki

Seite

93

Prof. Dr. U. Matecki

Seite

94

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die main()-Funktion wird in einer separaten Quelldatei


abgespeichert. Deren Name ist der des spter aufzurufenden Programms mit der Endung .c . Diese Quelldatei enthlt nur (!!!) die main()-Funktion und evtl. notwendige
#include-Direktiven.

int main()
{
float test;
test = gibFloatZurueck();
return 0;
}

Alle anderen Funktionen werden in weiteren, separaten


Quelldateien mit der Endung .c gespeichert.

Zu beachten:

Zu diesen Quelldateien werden zugehrige, namensgleiche Headerdateien mit der Endung .h geschrieben. Sie
enthalten alle notwendigen #defines und die Deklaration
der Funktionsprototypen aller Funktionen, die spter von
aussen aufrufbar sein sollen.
Die #ifndef-Anweisung ist fr den Preprozessor. Sie bewirkt, da der Code zwischen #ifndef und #endif nur
genau einmal beim bersetzen in den Quellcode eingebunden wird.
Sie ist notwendig, weil Funktionsprototypen und Konstanten nicht mehrfach deklariert werden drfen (Compilerfehler!!).

o gibFloatZurueck mu mit float als Rckgabewert definiert werden.


o Die aufrufende Funktion (hier: main()) muss wissen,
da gibFloatZurueck() den Datentyp float zurckliefert.
 Wenn gibFloatZurueck nicht in der gleichen
Quelldatei vor der aufrufenden Funktion (hier: Funktion main()) definiert ist, mu ihr Prototyp vor dem
Aufruf deklariert werden.
Bei Funktionen, die dem Aufrufer nicht bekannt sind,
wird sonst implizit angenommen, da sie den Datentyp int zurckliefern!  ggf. unsinnige Ergebnisse.
Beispiel:

 Funktionen ohne int- Resultat:


Bisher betrachtet: Funktionen, die keinen Wert zurckliefern oder einen int- Wert zurckliefern.
Jetzt: Funktionen mit float oder double als Rckgabewert.

float gibFloatZurueck()
{
return 1.85;
}

Beispiel:
float gibFloatZurueck()
{
return 1.85;
}

Prof. Dr. U. Matecki

float gibFloatZurueck();
int main()
{
float test;
test = gibFloatZurueck();
return 0;
}

Seite

95

Prof. Dr. U. Matecki

Seite

96

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Diese "Forward-Deklaration" kann sogar innerhalb


der aufrufenden Funktion, vor dem Aufruf geschehen:

1.2.6.3 Parameterbergabe
 Bisher behandelt: Funktionen ohne bergabeparameter

int main()
{
float test;
float gibFloatZurueck();

 Jetzt: Funktionen mit bergabeparametern


 In C funktioniert Parameterbergabe ausschlielich ber
"call by value", d. h. eine Kopie des Parameterwertes wird
der Funktion bergeben.

test = gibFloatZurueck();
return 0;
}

 Beispiel 1:

float gibFloatZurueck()
{
return 1.85;
}

Fr PE2 gilt jedoch: Funktionsprototypen werden immer sauber in einer Headerdatei deklariert, die anschlieend eingebunden wird!

float plusF(float wert1, float wert2)


{
return wert1 + wert2;
}
int main()
{
float w1 = 1.5;
float w2 = 2.1;
float result;

1.5
2.1

1.5
2.1

result = plusF(w1, w2);


return 0;
}

Die Werte der Variablen w1 und w2 liegen im Speicherbereich (Stack) der Funktion main(). Diese Werte werden
beim Funktionsaufruf von plusF in den Stack der Funktion plusF() kopiert. Die Funktion plusF() addiert die
Werte der Kopien und liefert das Ergebnis an die
aufrufende Funktion (hier main() ) zurck.

Prof. Dr. U. Matecki

Seite

97

Prof. Dr. U. Matecki

Seite

98

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Konsequenz von diesem "call by value": die aufgerufene


Funktion kann nur die Werte ihrer Kopien ndern, nicht
aber die Werte der Variablen des aufrufenden Programms:
Beispiel 2:

int aendere(int wert)


{
return ++wert;
}

1.2.6.4 Rekursiver Funktionsaufruf


Funktionen knnen sich in C selbst aufrufen. Ebenso knnen
sie Funktionen aufrufen, die wiederum sie selbst aufrufen.
Funktionen, die sich selbst direkt oder indirekt aufrufen, werden
als rekursive Funktionen bezeichnet.
Im Gegensatz dazu werden Funktionen, die sich nicht selbst
aufrufen, als iterative Funktionen bezeichnet.

3
4

Beispiel 1: Fakulttsberechnung
Gesucht ist eine Funktion

int main()
{
int w = 3;

unsigned long fakulataet( int n );

3
int result;

die von einer Zahl n die Fakultt


n! = n * (n-1) * (n-2) * ... * 1
0! = 1
1! = 1
berechnet.

result = aendere(w);
return 0;
}

Der Parameter wert der Funktion aendere() nimmt einen vernderten Wert an. Die Variable w der aufrufenden
Funktion main() bleibt jedoch auch nach dem Aufruf der
Funktion aendere() unverndert.

Prof. Dr. U. Matecki

Seite

99

Man kann n! auch folgendermaen schreiben:


n! = n * (n-1)!

Prof. Dr. U. Matecki

Seite 100

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 2 Mglichkeiten der Implementation:

rekursion.c:

rekursion1/rekursion1Funcs.c:

#include "rekursion1Funcs.h"
int main()
{
unsigned long i, r;
i = fakultaetIter(5);
r = fakultaetRek(5);

#include <stdio.h>
/* Iterative Berechnung von n! */
unsigned long fakultaetIter(unsigned int n)
{
unsigned long i,j;
for (i=n-1, j=n; i>1;i--)
{
j *= i;
}
return j;
}

printf("Iteratives Ergebnis: %ld\n", i);


printf("Rekursives Ergebnis: %ld\n", r);
return 0;
}

Was tun die beiden Funktionen fakultaetIter() und


fakultaetRek() ?

/* Rekursive Berechnung von n! */


unsigned long fakultaetRek(unsigned int n)
{
printf("Rekursion mit %d\n", n);
if (n <= 1)
{
return 1;
}
else
{
return (n*fakultaetRek(n-1));
}
}

 fakultaetIter() berechnet die Fakulttsformel mit einer rckwrts gezhlten for-Schleife und implementiert
damit die erste Variante der Formel.
 fakultaetRek () enthlt hingegen keine Schleife, sondern folgende Konstruktion (n=5):

 5*4*3*2*1

fakultaetRek(5)

rekursion1/rekursion1Funcs.h:
5 *

#ifndef REKURSION1_H
#define REKURSION1_H

fakultaetRek(4)
4 *

 4*3*2*1

fakultaetRek(3)

unsigned long fakultaetIter(unsigned int n);


unsigned long fakultaetRek(unsigned int n);
3 *

fakultaetRek(2)

#endif
2 *

 3*2*1

 2*1

fakultaetRek(1)

 1

Prof. Dr. U. Matecki

Seite 101

Prof. Dr. U. Matecki

Seite 102

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die Funktion ruft sich selbst so oft auf, bis ein Aufruf in der
ersten if-Anweisung landet. Die bis dahin durchgefhrten
und noch nicht zurckgekehrten Aufrufe werden in einem
separaten Speicherbereich des Rechners "gestapelt"
(Stack). Sobald die unterste Ebene zurckkehrt, hat der
darberliegende Aufruf das Ergebnis, welches er mit seinem n multiplizieren mu.

2 Fortgeschrittene Sprachkonstrukte
2.1

Arrays

2.1.1 Eindimensionale Arrays (Vektoren)


2.1.1.1 Eindimensionale Arrays fester Gre

 Vorteile rekursiver Funktionen:


o fr manche Problemstellungen leichter zu implementieren, weil ihre Anwendung eher der menschl. Vorstellung entspricht.
 Nachteile rekursiver Funktionen:
o manchmal langsamer als iterative Implementierung,
da Funktionsaufrufe mehr Laufzeit kosten als geschickt implementierte Schleifen + if-Abfragen.

Der einfachste zusammengesetzte Datentyp in C ist das Array


oder deutsch: Vektor bzw. Feld. Er fat mehrere Objekte gleichen Typs zu einer Einheit zusammen und ordnet sie linear
aufeinanderfolgend im Speicher an.
Arrays werden durch die Verwendung eckiger Klammern [ ]
nach dem Variablennamen erzeugt.
Beispiel:
int iArray[10];

o stack-overflow bei hohen Rekursionstiefen


legt ein Array mit 10 zusammenhngenden int-Elementen im
Hauptspeicher an.

4 Byte int
4 Byte int
4 Byte int
.
.
.
4 Byte int

Prof. Dr. U. Matecki

Seite 103

Prof. Dr. U. Matecki

Seite 104

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Der Zugriff auf die Elemente eines Arrays erfolgt durch Angabe
des Variablennamens, gefolgt von dem Element-Index in eckigen Klammern:

ohne Angabe der Arraygre: (Gre wird implizit aus der Anzahl der Initialisierungselemente abgeleitet):
int iArray[ ] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
};

for (i=0; i<10; i++)


{
iArray[i] = 5;
}
Index:
0
1

Die Gre eines Arrays kann mit dem sizeof-Operator abgefragt werden, sofern sie in der Vereinbarung bereits festgelegt
wurde:

...
5

9
5

int iArray[10];
int size = sizeof(iArray);

!!! Der Index des ersten Elements eines n-elementigen Feldes


ist 0  der Index des letzten Elementes eines n-elementigen
Feldes ist n-1 !!!
ACHTUNG: Der C-Compiler gibt keine Warnung aus, falls Array-Grenzen in Schleifen u. . verletzt werden !!!
Arrays knnen auch direkt in der Vereinbarung initialisiert werden, sofern sie als globale Variable (Speicherungsklasse
extern) vereinbart wurden.
Mglichkeit 1: Vollstndige Initialisierung in der Vereinbarung
mit Angabe der Arraygre:
int iArray[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
};

ACHTUNG:
Arrays und Adressen: Der Variablenname des Arrays bezeichnet in C die Anfangsadresse des Arrays im Hauptspeicher:
Die Anweisung
printf("%d", iArray);
gibt die Adresse der Stelle im Hauptspeicher aus, an der das
Array beginnt !!!
int iArray[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
Index:
0
1
2
...
9
5

Mglichkeit 2: Teilweise Initialisierung in der Vereinbarung mit


Angabe der Arraygre: (Rest wird mit 0en initialisiert):
iArray

int iArray[10] = { 5, 5, 5, 5 };

Mglichkeit 3: Vollstndige Initialisierung in der Vereinbarung


Prof. Dr. U. Matecki

Seite 105

Prof. Dr. U. Matecki

Seite 106

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

!!! Was passiert bei folgender Anweisungsfolge ?!!!!

Abschlieendes Beispiel:

int iArray[5] = {3, 3, 3, 3, 3};


iArray = 5;

array1/array1.c:
#include <stdio.h>
/* Die ersten 5 Elemente des Arrays werden mit 5 vorbelegt.
* Der Rest wird unter ANSI-C mit 0en initialisiert, wenn
* nichts anderes angegeben ist
*/
int iArray1[10] = {5, 5, 5, 5, 5};
int iArray2[] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
int main()
{
int i, elementsArray1, elementsArray2;
/* Anfangsadresse von iArray1 ausgeben: */
printf("Gebe iArray1 aus: %d Groesse: %d\n",
iArray1,
sizeof(iArray1));
/* Anzahl Elemente von iArray1 und iArray2 berechnen */
elementsArray1 = sizeof (iArray1)/sizeof(int);
elementsArray2 = sizeof (iArray2)/sizeof (int);
/* Elemente von iArray1 und iArray2 ausgeben; */
for (i=0;i<elementsArray1;i++)
{
printf("iArray2[%d] = %d\n", i, iArray1[i]);
}
printf("\n\n");
for (i=0;i<elementsArray2;i++)
{
printf("iArray2[%d] = %d\n", i, iArray2[i]);
}
return 0;
}

Prof. Dr. U. Matecki

Seite 107

Prof. Dr. U. Matecki

Seite 108

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Testlauf array1:
Gebe iArray1
iArray2[0] =
iArray2[1] =
iArray2[2] =
iArray2[3] =
iArray2[4] =
iArray2[5] =
iArray2[6] =
iArray2[7] =
iArray2[8] =
iArray2[9] =

aus: 134518208 Groesse: 40


5
5
5
5
5
0
0
0
0
0

iArray2[0]
iArray2[1]
iArray2[2]
iArray2[3]
iArray2[4]
iArray2[5]
iArray2[6]
iArray2[7]
iArray2[8]
iArray2[9]

5
5
5
5
5
5
5
5
5
5

=
=
=
=
=
=
=
=
=
=

2.1.1.2 C99: Eindimensionale Arrays variabler


Lnge (VLAs)
Ab dem C99-Standard ist auch folgendes erlaubt:
array2/array2.c:
#include <stdio.h>
#include <limits.h>
/*
* gibt die tatsaechlich reservierte Groesse eines VLA aus
*/
void vlatest()
{
unsigned int n;
printf("n eingeben: ");
scanf("%d",&n);
int myarray[n];
printf("\nmyarray ist %d Plaetze gross!\n",
sizeof(myarray)/sizeof(n));
}
int main()
{
vlatest();
}

Diese Arrays werden als Variable Length Arrays (VLAs)


bezeichnet, da ihre Gre ber zur Laufzeit vernderliche
Variablen festgelegt wird.
Auch als Parameter von Funktionen sind VLAs erlaubt. Dabei
muss die Grenangabe mit bergeben werden:
void foo(int size, int myarray[size])
{
/* tu irgendwas mit myarray */
}
Prof. Dr. U. Matecki

Seite 109

Prof. Dr. U. Matecki

Seite 110

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Unterschied von VLAs zu den vorher angelegten Arrays:


Hierbei handelt es sich um Laufzeit-Arrays.

2.1.2 Strings (Zeichenketten)

 Das Array wird erst angelegt, nachdem sich zur Laufzeit entschieden hat, wie gro n wird.
 Bisher: In den frheren Kapiteln wurde die Array-Gre bereits zum bersetzungs-Zeitpunkt festgelegt.
ACHTUNG: VLAs knnen nur als lokale Variablen oder Parameter angelegt werden. Sie werden beim Verlassen der
Funktion wieder freigegeben.
ACHTUNG Fehlerquelle:
Beim Anlegen eines VLA wissen Sie nie, ob Sie auch wirklich
ausreichend Arraypltze bekommen haben, da die Anzahl ihrer
Pltze auf den maximalen positiven Wertebereich von int beschrnkt ist. Das VLA kann also kleiner sein, als Sie glauben!
Daher sind VLAs in unserem Praktikum verboten, es sei denn,
sie werde in der Aufgabenstellung ausdrcklich verlangt. Statt
dessen verwenden Sie die spter erklrten Zeiger mit dynamischer Speicherplatzreservierung per malloc()!

2.1.2.1 Struktur von Strings in C


In Java gibt es eine spezielle String-Klasse. In C mssen wir
uns mit char-Arrays behelfen.
 In C sind Zeichenketten spezielle, eindimensionale char-Arrays mit mehreren Besonderheiten:
 Sie enden mit '\0' (Stringende-Zeichen).
 Sie knnen auch als lokale Variable (Speicherungsklasse
auto) initialisiert werden.
 Die Initialisierung selbst kann folgendermaen durchgefhrt werden:
Mglichkeit 1: Wie andere Arrays:
char myStr[ ] = {'m', 'e', 'i', 'n', 's',
't', 'r', 'i', 'n', 'g', '\0'};
Hierbei mu, falls das char-Array als String interpretiert
werden soll, das Stringende-Zeichen explizit angegeben
werden.
Mglichkeit 2: Abgekrzte Form in doppelten Hochkommata und teilweiser Initialisierung:
char myStr[15] = "meinstring" ;
Der Rest des Strings wird hier automatisch mit '\0'en
initialisiert.

Prof. Dr. U. Matecki

Seite 111

Prof. Dr. U. Matecki

Seite 112

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Mglichkeit 3: Abgekrzte Form in doppelten Hochkommata:

2.1.2.2 Die Zeichenketten-Funktionen aus der


C-Standard-Bibliothek

char myStr[ ] = "meinstring" ;

Um Zeichenketten zu bearbeiten, stellt die C-Standardbibliothek


bereits Funktionen z. B. fr

Bei dieser Mglichkeit fgt das System selbststndig das


'\0'-Zeichen an das Array an.
 char-Arrays, welche nicht mit dem '\0' Zeichen terminiert
werden, werden von (spter genutzten) String-Funktionen
oder von printf() nicht als Strings erkannt. (!!!)

 Kopiervorgnge
 Konkatenation
 Zeichen/Substring-Suche
 Tokenizing
zur Verfgung. Um sie verwenden zu knnen, mu die
Headerdatei string.h eingebunden werden. Der folgende
Abschnitt gibt zunchst einen berblick ber die gngigsten CZeichenkettenfunktionen. Anschlieend werden einige dieser
Funktionen durch kleine Code- und Bildbeispiele genauer
dargestellt.
ACHTUNG: Die Funktionssignaturen der ZeichenkettenFunktionen verwenden als bergabeparameter char-Zeiger
(also char * ). Wir wenden die Funktionen jedoch in den
ersten Beispielen mit herkmmlichen char-Arrays an.

Prof. Dr. U. Matecki

Seite 113

Prof. Dr. U. Matecki

Seite 114

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Gesamtberblick zum spteren Nachschlagen:

vergleicht die Zeichenketten cs und ct.


- Wenn (alphabetische Ordnung!) cs < ct: Rckgabewert < 0.
- Wenn cs == ct: Rckgabewert == 0.
- Wenn cs > ct: Rckgabewert > 0.
ACHTUNG: Beide Zeichenketten mssen mit '\0' abgeschlossen sein.

Der Typ size_t heisst bei uns (auf unserem System) nichts
anderes als int. Sie knnen fr den Parameter n also getrost
eine int-Variable oder eine mit #define definierte, symbolische Konstante fr einen int-Wert bergeben! Ich habe hier
die Funktionsprototypen genau so dargestellt, wie sie auf den
meisten man-Pages nachzulesen sind. Daher fr n der Datentyp size_t, der einfach eine ganzzahlige Gren- bzw.
Zahlenangabe bedeutet.
char *strcpy(char *s, const char *ct);
kopiert die Zeichenkette ct inklusive '\0' in das Array s. Liefert s
zurck. Achtung: s muss vor dem Funktionsaufruf allokiert
worden sein und ct muss eine gltige Zeichenkette mit '\0' als
Abschlu sein.
char *strncpy(char *s, const char *ct, size_t
n);
kopiert hchstens n Zeichen der Zeichenkette ct in das Array s.
Liefert s zurck. Wenn unter diesen n Zeichen kein \0 als
Abschlu steht, so ist das Ergebnis in s nicht \0-terminiert!
char *strcat(char *s, const char *ct );
hngt Zeichenkette ct hinten an s an.
ACHTUNG: beide Zeichenketten mssen vor dem Aufruf mit '\0'
abgeschlossen sein. Das Array s mu gengend Speicher bereitstellen. um die neue Zeichenkette incl. '\0'aufzunehmen.
char *strncat(char *s, const char *ct, size_t
n);
hngt hchstens n Zeichen der Zeichenkette ct hinten an s an.
ACHTUNG: beide Zeichenketten mssen vor dem Aufruf mit '\0'
abgeschlossen sein. Das Array s mu gengend Speicher
bereitstellen. um die neue Zeichenkette incl. '\0'aufzunehmen.

int strcmp(const char *cs, const char *ct);


Prof. Dr. U. Matecki

Seite 115

int strncmp(const char *cs, const char *ct,


size_t n);
vergleicht hchstens n Zeichen der Zeichenketten cs und ct.
- Wenn (alphabetische Ordnung!) cs < ct: Rckgabewert < 0.
- Wenn cs == ct: Rckgabewert == 0.
- Wenn cs > ct: Rckgabewert > 0.
ACHTUNG: Beide Zeichenketten mssen mit '\0'
abgeschlossen sein.
char *strchr(const char *cs, int c);
Liefert Zeiger auf die Stelle in cs, in der das Zeichen c das erste
Mal vorkommt, oder NULL, falls
c nicht in cs vorkommt.
ACHTUNG: cs muss eine gltige Zeichenkette mit '\0' als
Abschlu sein.
char *strrchr(const char *cs, int c);
Liefert Zeiger auf die Stelle in cs, in der das Zeichen c das
letzte Mal vorkommt, oder NULL, falls
c nicht in cs vorkommt.
ACHTUNG: cs muss eine gltige Zeichenkette mit '\0' als
Abschlu sein.
char *strstr(const char *cs, const char *ct);
Liefert den Zeiger auf die Stelle in cs, ab der der Teilstring ct in
cs das erste Mal vorkommt, oder NULL, falls
der Teilstring ct nicht in cs vorkommt.
ACHTUNG: Beide Zeichenketten mssen mit '\0'
abgeschlossen sein.
size_t strlen(const char *cs);
Prof. Dr. U. Matecki

Seite 116

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Liefert die Lnge der Zeichenkette cs (ohne abschlieendes


'\0'-Zeichen) zurck.

Beispiel 2: Aneinanderhngen von Zeichenketten mit


strcat() und strncat():
:

Beispiel 1: Kopieren von Zeichenketten mit strcpy() und


strncpy():

char str11[MAX] = "Kein Teststring";


char str1[MAX] = "Kein Teststring";
char str3[MAX] = " zum Testen";
strcat(str1, str3);
str1:

char str1[MAX] = "Kein Teststring";


char str2[MAX] = "Alter String";

Kein Teststring zum Testen\0

str3:
zum Testen\0

strncpy(str2, str1, 3);


str1:
str2:
Keilter String\0

Kein Teststring\0\0

Hnge str3 an str1 an

Kopiere n=3 Zeichen von str1 nach str2, OHNE nach


dem n-ten Zeichen eine \0 anzufgen  Die alten Zeichen
hinter "Kei" bleiben in str2 erhalten
strcpy(str2, str1);
str2:

str1:

Kein Teststring\0

Kein Teststring\0\0

strncat(str11, str3,3);
str11:
Kein Teststring zu\0

str3:
zum Testen\0

Hnge n=3 Zeichen von str3 an str1 an

Kopiere str1 nach str2

Prof. Dr. U. Matecki

Seite 117

Prof. Dr. U. Matecki

Seite 118

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel 3: Vergleichen von Zeichenketten mit strcmp():


Die Funktion strncmp() arbeitet gleich; Sie vergleicht jedoch
nur ber n Zeichen (dritter Parameter)

2.1.3 Mehrdimensionale Arrays

char str1[MAX] = "Keks";


char str2[MAX] = "Geks";
int i;

C kennt nicht nur eindimensionale, sondern auch mehrdimensionale Arrays beliebiger, auch selbst-definierter Datentypen.

i= strcmp(str1, str2);
str1:
Keks\0

2.1.3.1 Mehrdimensionale Arrays fester Gre

Vereinbarung und Initialisierung mehrdimensionaler Arrays:


str2:

Mglichkeit 1: Die Gren der einzelnen Dimensionen werden


bei der Vereinbarung angegeben:

Geks\0
i > 0, da str1 > str2 in alphabet. Ordnung

i= strcmp(str2, str1);
str1:
Keks\0

unsigned char aArray [3][5] =


{1, 2, 3, 4, 200},
{33, 33, 33, 33, 34},
{200, 0, 1, 1, 1}
};

str2:
Geks\0

i < 0, da str2 < str1 in alphabet. Ordnung

i= strcmp(str2, str2);
str2:
2eks\0

Mglichkeit 2: Die erste Dimension kann weggelassen werden,


falls das Array ein bergabeparameter einer Funktion ist und
sich die Arraygre eindeutig aus der Initialisierung ergibt:

str2:

void myfunction (unsigned char aArray [][5])


{

Geks\0

i == 0, da str2 und str2 in alphabet. Ordnung gleich sind

.;
}
Der Zugriff auf Elemente mehrdimensionaler Arrays erfolgt mit
Mehrfachanwendung von [ ] Klammern:
unsigned char a;
a = aArray[1][2];
Nicht erlaubt ist folgende Syntax:
a = aArray[1, 2];

Prof. Dr. U. Matecki

Seite 119

Prof. Dr. U. Matecki

Seite 120

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel 1: Einfache Anwendung mehrdimensionaler Arrays:


Ausgabe einer Matrix (2-D) und eines Quaders (3-D) auf den
Bildschirm:

void printMatrix()
{
int i, j;

array3/array3Funcs.c:

for(i=0;i<ZEILEN; i++)
{
for(j=0;j<SPALTEN; j++)
{
printf("%d ",matrix[i][j]);
}
printf("\n");
}
printf("=================================\n");

#include <stdio.h>
/*Globale Matrix-Variable als 2-dim. Array: 3 Zeilen, 4 Spalten */
#define ZEILEN 3
#define SPALTEN 5
#define EBENEN 2
int matrix[ZEILEN][SPALTEN] =
{
{2,2,2,2,2},
{3,3,3,3,3},
{4,4,4,4,4}
};

}
void printQuader()
{
int i, j, k;
for(i=0;i<ZEILEN; i++)
{
for (k=0; k<EBENEN; k++)
{
for(j=0;j<SPALTEN; j++)
{
printf("%d ",quader[k][i][j]);
}
printf("\t");
}
printf("\n");
}
printf("=================================\n");

/*Globale 3-d-Matrix (Quader): 2 Ebenen mit je 3 Zeilen und 5 Spa


int quader[EBENEN][ZEILEN][SPALTEN] =
{
{
{2,2,2,2,2},
{3,3,3,3,3},
{4,4,4,4,4}
},
{
{7,7,7,7,7},
{8,8,8,8,8},
{9,9,9,9,9}

}
};

Prof. Dr. U. Matecki

Seite 121

Prof. Dr. U. Matecki

Seite 122

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

array3/array3Funcs.h:

2.1.3.2 C99: Mehrdimensionale Arrays variabler


Gre

#ifndef ARRAY3FUNCS_H
#define ARRAY3FUNCS_H
void printMatrix();
void printQuader();
#endif

Zum Selbststudium: Der C99-Standard kennt auch mehrdimensionale VLAs. Auch sie knnen entweder als lokale Variablen oder als Parameter angelegt werden. Das folgende Programm ist eine Abwandlung des Matrixprogramms array3:

array3/array3.c:

array4/array4Funcs.c:

#include <stdio.h>
#include "array3Funcs.h"

#include <stdio.h>
int printMatrix2(int zeilen, int spalten,
int matrix[zeilen][spalten])
{
int i, j;

int main()
{
printMatrix();
printQuader();
return 0;
}

for(i=0;i<zeilen; i++)
{
for(j=0;j<spalten;j++)
{
printf("%d ",matrix[i][j]);
}
printf("\n");
}
}
array4/array4Funcs.h:
#ifndef ARRAY4FUNCS_H
#define ARRAY4FUNCS_H
void printMatrix2(int zeilen, int spalten,
int matrix[zeilen][spalten]);
#endif

Prof. Dr. U. Matecki

Seite 123

Prof. Dr. U. Matecki

Seite 124

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

#include "array4Funcs.h"

2.2 Zeiger und dynamische Speicherverwaltung

int main()
{
int i,j;
int zeilen = 2;
int spalten = 3;
int meinematrix[zeilen][spalten];

Bisher kennengelernt: Arrays verschiedener Datentypen in C.


Eine Arrayvariable enthlt die Anfangsadresse des eigentlichen
Arrays (vgl. Schild mit der Aufschrift: "B116"  verweist auf den
Raum, in dem sich 50 Elemente des Typs "Studierende" befinden).

array4/array4.c:

/*Da bei VLAs keine Initialisierungsliste


*moeglich, wird hier explizit in Schleifen
*initialisiert
*/
for(i=0;i<zeilen;i++)
{
for(j = 0; j<spalten; j++)
{
meinematrix[i][j]=i;
}
}

Neu: Allgemeine Variablen, die die Adresse irgendeines Objekts eines beliebigen Typs im Speicher enthalten  Zeiger
(engl. Pointer)

2.2.1 Speicherverwaltung 1 Zeiger,


Adressen und Zeigerarithmetik

printMatrix2(zeilen, spalten, meinematrix);


}

Manchmal wird nicht der Wert eines Objekts, z. B. einer intVariablen bentigt, sondern seine Adresse.
Eine Variable, die die Adresse eines Objekts enthlt, wird als
Zeiger (engl. Pointer) auf das Objekt bezeichnet.
Pointervariablen knnen mit dem *-Operator deklariert werden:
int *ptrInt;
Auch die Adresse einer "normalen" Variablen kann mit einem
speziellen Operator, dem sog. Adreoperator & abgefragt werden:
int i=32;
int *ptr_i;
ptr_i = &i;  ptr_i enthlt die Adresse
i

Prof. Dr. U. Matecki

Seite 125

Prof. Dr. U. Matecki

Seite 126

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Was geschieht hier?


Annahme:
i steht im Hauptspeicher an der Adresse 0xBB80 (4800010)

Annahme:
i steht im Hauptspeicher an der Adresse 0xBB80 (4800010)
...
0xBB78
0xBB7C
0xBB80
0xBB84

0000 0000

ptr_i = &i;

0000 0000

...

0000 0000

0010 0000

0xBB78
0xBB7C
0xBB80
0xBB84

...
...
i enthlt den Wert 3210
ptr_i enthlt den Wert 0xBB80

0000 0000

ptr_i = &i;
j = *ptr_i;

Der tatschliche int-Wert der obigen Variablen ptr_i kann mit


dem *-Operator abgefragt werden:

int i=32;
int *ptr_i;
int j;
ptr_i = &i;
j = *ptr_i;

...

0000 0000

...

0000 0000

0010 0000

...
...
i enthlt den Wert 3210
ptr_i enthlt den Wert 0xBB80
j enthlt den Wert, der an Speicherstelle 0xBB80
steht

Beispiel 1:
Simulation von "Call by Reference". Aus Kap. 1.2.6.3 bekannt:
Funktionen in C kennen nur die bergabe von Werten (Call by
value)  Der Inhalt bergebener Variablen kann nicht verndert werden.
Aber: Wir knnen die Adresse von Variablen bergeben.  Die
Funktion ndert den Wert, der an der bergebenen Adresse
steht:

 Abfragen des Inhalts an


der Adresse in ptr_i

pointer1/pointer1Funcs.c
void swap1(int var1, int var2)
{
int tausch;
printf("Start swap1: var1 = %d, var2 = %d\n",var1, var2);
tausch = var1;
var1 = var2;
var2 = tausch;
printf("Ende swap1: var1 = %d, var2 = %d\n",var1, var2);
}

Prof. Dr. U. Matecki

Seite 127

Prof. Dr. U. Matecki

Seite 128

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


ACHTUNG: mit Zeigern kann man in C auch auf nicht reservierte Speicherpltze zugreifen. Dies kann (und wird i. d. R.) zu
Programmabstrzen fhren:

void swap2(int *var1, int *var2)


{
int tausch;

Beispiel:
printf("Start swap2: *var1 = %d, *var2 = %d\n",*var1, *var2);
tausch = *var1;
*var1 = *var2;
*var2 = tausch;
printf("Ende swap2: *var1 = %d, *var2 = %d\n",*var1, *var2);
}

int i = 32;
int *ptr_i;
int j;
ptr_i = &i;
j = *(ptr_i + 1);

/*1.
/*2.
/*3.
/*4.
/*5.

*/
*/
*/
*/
*/

pointer1/pointer1Funcs.h

Was geschieht hier?

#ifndef POINTER1_H
#define POINTER1_H

Anweisung 1 reserviert 4 Byte Speicherplatz fr eine int-Variable.


Anweisung 2 definiert einen Pointer auf eine int-Variable (noch
nicht initialisiert).

void swap1(int var1, int var2);


void swap2(int *var1, int *var2);
#endif
pointer1/pointer1.c

Anweisung 3 reserviert weitere 4 Byte Speicherplatz fr eine


int-Variable.

#include <stdio.h>
#include "pointer1Funcs.h"

Anweisung 4 schreibt die Startadresse der in Anweisung 1 reservierten 4 Byte in ptr_i.

int main()
{
int i=5, j=7;
printf("Vor Tausch: i = %d, j = %d\n", i,j);
swap1(i, j);
printf("Nach Tausch mit swap1: i = %d, j = %d\n", i,j);
swap2(&i, &j);
printf("Nach Tausch mit swap1: i = %d, j = %d\n", i,j);
return 0;

Prof. Dr. U. Matecki

Seite 129

Anweisung 5 greift auf den nchsten int-Speicherplatz hinter


der Adresse von i.
 Anweisung 5 zeigt, dass Zeiger hnlich wie ganzzahlige
Datentypen inkrementiert, dekrementiert, addiert und
abgezogen werden. Beim Inkrementieren / Dekrementieren
ganzzahliger Werte von Zeigervariablen werden hier die
"Speicherpltze" des jeweiligen Datentyps herauf- und
heruntergezhlt.

Prof. Dr. U. Matecki

Seite 130

Handout Programmentwicklung II -- C und C++


...
ptr_i-1
ptr_i
ptr_i+1

0000 0000

0000 0000

Handout Programmentwicklung II -- C und C++

...

0000 0000

2.2.2 Speicherverwaltung 2 Zeiger und


Arrays
0010 0000

In C gibt es einen sehr engen Zusammenhang zwischen Zeigern und Arrays:

...
...
i enthlt den Wert 3210

short short_array[5];
j = *(ptr_i+1);

j enthlt den Wert, der an Stelle ptr_i + 1


steht

short *short_ptr;

Weitere Beispiele:

 Beide Variablen enthalten eine Adresse, allerdings enthlt


short_array eine Adrekonstante.

/* liefert Inhalt an Adresse ptr_i +1 */


j = *++ptr_i;

 Jede Arrayoperation kann in C auch mit Zeigern implementiert werden.

/* Inhalt an Adresse ptr_i und 1 dazu (hier 33)


*/
j = *ptr_i +1

Prof. Dr. U. Matecki

Seite 131

Prof. Dr. U. Matecki

Seite 132

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel:

pointer2/pointer2.c
#include <stdio.h>
#include "pointer2Funcs.h"

pointer2/pointer2Funcs.c
#include <stdio.h>
void arrayRueckwaerts()
{
short s_array[5] = {3,4,5,6,7};
int i;
for (i=4; i>=0;i--)
{
printf("s_array[%d] = %d\n",i,s_array[i]);
}

int main()
{
arrayRueckwaerts();
printf("\n");
pointerRueckwaerts();
return 0;
}

}
void pointerRueckwaerts()
{
short s_array[5] = {3, 4, 5, 6, 7};
short *s_pointer = s_array + 4;
int i;
for (i = 4;i>=0; i--, s_pointer--)
{
printf("*s_pointer = %d\n",*s_pointer);
}
}

pointer2/pointer2Funcs.h:
#ifndef POINTER2_H
#define POINTER2_H
void arrayRueckwaerts();
void pointerRueckwaerts();
#endif

Prof. Dr. U. Matecki

Seite 133

Prof. Dr. U. Matecki

Seite 134

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.2.3 Allokation von Zeigern

Was geschieht hier?

Bei der Deklaration statischer Arrays mu vor dem Programmstart bereits bekannt sein, wie viel Speicher bentigt wird.
Hufig stellt sich aber erst zur Laufzeit eines Programms heraus, wie gro ein Array wirklich sein mu. In diesem Fall wird
es mit einer der Funktionen
void *malloc(int size);
 allokiert size Bytes Speicher

 Der Rckgabewert von malloc() wird auf den tatschlichen Datentyp gecastet (sonst u. U. Compile-Fehler).
 Die Funktion malloc() allokiert einen zusammenhngenden Speicherbereich fr 5 short-Werte.
 Es wird abgefragt, ob malloc tatschlich Speicher allokiert hat oder NULL zurckgeliefert hat.
Lsung mit calloc():

void *calloc(int nobj, int size);


 allokiert einen zusammenhngenden Speicherbereich mit nobj Objekten, jedes size Bytes gro und
belegt diese mit 0en vor
dynamisch allokiert.
Vorsicht: Beide Funktionen liefern einen sog. void-Pointer zurck. Dies ist ein Pointer auf einen beliebigen Datentyp (Platzhalterdatentyp). Falls kein Speicher mehr frei ist, wird die Adresse NULL zurckgeliefert.
Typische Aufrufe:
short *s_ptr;
if ((s_ptr = (short *)malloc(5*sizeof(short)))
== NULL)
{
fehlerBehandlung();
}

Prof. Dr. U. Matecki

Seite 135

short *s_ptr;
if ((s_ptr = (short *)calloc(5,sizeof(short)))
== NULL)
{
fehlerBehandlung();
}
Sobald ein so reservierter Speicherbereich nicht mehr bentigt
wird, mu er mit der Funktion
void free(void *ptr);
wieder freigegeben werden, so da er anderen Programmen
oder anderen Allokierungsaufrufen in Ihrem Programm wieder
zur Verfgung steht.
ACHTUNG: Ein hufig gemachter Programmierfehler ist es,
Speicher (z. B. in Schleifen) laufend zu allokieren und nicht
nach Gebrauch wieder freizugeben. Irgendwann hat das Programm dann smtlichen Speicher des Rechners belegt und
nichts geht mehr!

Prof. Dr. U. Matecki

Seite 136

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel: Unsere pointerRueckwaerts()-Funktion mit dynamisch erzeugtem und spter wieder freigegebenen Speicherplatz:

Wenn Arrays innerhalb einer Funktion statisch vereinbart werden, werden sie vom System wieder freigegeben, sobald die
Funktion verlassen wird.
Ein hufiger Programmierfehler ist in diesem Zusammenhang
folgende Konstruktion:

pointer2/pointer2Funcs.c
....

char *gibArrayZurueck()
{
char array[] = "abcde";
return array;
}

void pointerMallocRueckwaerts()
{
short *s_pointer1;
short *s_pointer2;
int i;
if ((s_pointer1 = (short *) malloc (5*sizeof(short)))
== NULL)
{
return;
}

int main()
{
char *cPtr = gibArrayZurueck();
printf("%s\n",cPtr);

s_pointer2 = s_pointer1;
for (i=0; i<5; i++)
{
*s_pointer2++ = i+3;
}

for (i = 4;i>=0; i--)


{
printf("*s_pointer = %d\n",*--s_pointer2);
}

Die Funktion gibArrayZurueck() gibt zwar (korrekt) die Anfangsadresse ihres lokalen Arrays zurck, aber der Speicherplatz an dieser Adresse ist nach Rckkehr aus dieser Funktion
lngst wieder freigegeben, so da main() ihn nicht bei
printf() verwenden darf. Dies kann zu Programmabstrzen
fhren.

free (s_pointer1);
}

Prof. Dr. U. Matecki

Seite 137

Prof. Dr. U. Matecki

Seite 138

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Richtige Lsung:
char *gibArrayZurueck()
{
char *array;
if ((array =
(char*)malloc((strlen("abcde")+1)*sizeof(char)))
== NULL)
{
return NULL;
}
else
{
strcpy(array,"abcde");
return array;
}
}

2.2.4 Speicherverwaltung 3: Arrays aus


Zeigern
2.2.4.1 Grundlagen
Eine weitere hufig verwendete Konstruktion in C sind Arrays
aus Zeigern. Sie erinnern an mehrdimensionale Arrays, sind jedoch noch flexibler:
#define MAXLINE 6
char *linePtr[MAXLINE];
maier

int main()
{
char *cPtr = gibArrayZurueck();
if (cPtr == NULL)
{
printf("War nix\n");
}
else
{
printf("%s\n",cPtr);
}

hasenfuss
winterbottom
ruebezahl
giftzwerg
mueller

return 0;
}

Diese Konstruktion wird hufig fr komplexere Sortiervorgnge,


z. B. fr das alphabetische Sortieren von Zeichenketten verwendet. Hierbei werden nur die Zeiger "umgebogen", nicht aber
der Zeichenketten-Inhalt selbst umgespeichert (Laufzeitersparnis).

Prof. Dr. U. Matecki

Seite 139

Prof. Dr. U. Matecki

Seite 140

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Allerdings mu hier fr jede Zeichenkette separat Speicher allokiert werden. Dies kann entweder statisch geschehen mit:

Das folgende Programm zeigt einen solchen ZeichenkettenSortieralgorithmus (Bubble-Sort):

#define ELEMENTS 6
char *str1[ELEMENTS] = {"maier",
"hasenfuss",
"winterbottom",
"ruebezahl" ,
"giftzwerg",
"mueller"

pointer4/pointer4Funcs.c
#include "pointer4Funcs.h"
void bubbleSort(char *ptrArray[], int elements)
{
int i, j;
for(i=0;i<elements;i++)
{
for(j=0;j<elements;j++)
{
if(strcmp(ptrArray[j],ptrArray[i])<0)
{
swap(ptrArray,i,j);
}
}

};
oder dynamisch mit 6 malloc()-Aufrufen.

}
}

void swap(char *ptrArray[], int i, int j)


{
char *tmp;
/* Vertausche die Adressen an den Stellen i und j
* ACHTUNG: Hier werden nicht die Zeichen selbst
* umgespeichert, sondern nur Zeiger umgebogen
*/
tmp = ptrArray[i];
ptrArray[i] = ptrArray[j];
ptrArray[j] = tmp;
}

Prof. Dr. U. Matecki

Seite 141

Prof. Dr. U. Matecki

Seite 142

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.2.4.2 Anwendung von Arrays aus Zeigern:


Der Argumentenvektor der main()-Funktion

pointer4/pointer4.c:

#include <stdio.h>
#include pointer4Funcs.h
#define ELEMENTS 6
char *str1[ELEMENTS] = {"maier",
"hasenfuss",
"winterbottom",
"ruebezahl" ,
"giftzwerg",
"mueller"

Bisher verwendet: main()-Funktion mit Rckgabewert, aber


ohne Parameter.
Jetzt: Parameterbergabe ber die Kommandozeile
Beispielanwendung: echo-Programm, welches die ber Kommandozeile bergebenen Parameter auf die Konsole ausgibt.

};

Beispielaufruf:

int main()
{
printArray(str1,ELEMENTS);
bubbleSort(str1, ELEMENTS);
printArray(str1,ELEMENTS);

echo Hallo Kurs Programmentwicklung 2


bzw. in einem Java-Programm
java echo Hallo Kurs Programmentwickung 2

return 0;

soll ausgeben:

Hallo Kurs Programmentwicklung 2


Achtung: Was ist der Unterschied zwischen den beiden folgenden Vereinbarungen?
/*1.
*/
int a[10][20];
/* 2.
int *b[10];

*/

Bei Vereinbarung 1 wird ein 2-dimensionales Array definiert, d.


h. hier wird Platz reserviert fr 10 * 20 int-Speicherpltze:
Bei Vereinbarung 2 wird ein Array aus 10 int-Zeigern definiert.
Hier wurde also nur Platz fr 10 Adressen reserviert!!!
Prof. Dr. U. Matecki

Seite 143

Prof. Dr. U. Matecki

Seite 144

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Lsung in Java:

Lsung in C:

Java/echo/Echo.java:

echo/echo.c:

public class Echo{

#include <stdio.h>

public static void main(String[] args) {


for (int i=0; i<args.length;i++)
{
System.out.print(args[i]+" ");
}
System.out.println();
}
}

// end main
// end class Echo

 In C hat die main()-Funktion zwei bergabeparameter:

 In Java hat die main()-Methode einen bergabeparameter,


nmlich ein Feld, bestehend aus Strings. Das erste Element in
diesem Array (Index 0) ist das erste bergebene Argument. Bei
obigem Aufruf htte args[0] also den Inhalt "Hallo".
Die Anzahl der Feldelemente wird mit length abgefragt.

Prof. Dr. U. Matecki

int main(int argc, char *argv[])


{
int i;
for (i=0;i<argc;i++)
{
printf("%s ",argv[i]);
}
return 0;
}

Seite 145

 argc ist ein int-Wert, der die Anzahl der ZeichenkettenElemente in argv enthlt
 argv ist ein Zeiger auf einen oder mehrere char-Arrays,
die als Zeichenketten interpretiert werden. Das erste
Element von argv beinhaltet den Programmnamen. Die
nachfolgenden Argumente beinhalten die bergebenen
Kommandozeilenargumente.

Prof. Dr. U. Matecki

Seite 146

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Bei unserem Beispiel:


Abschlieend folgt ein Beispiel zum Selbststudium, welches
zeigt, wie in C typischerweise Optionen, die per Kommandozeile bergeben werden, ausgewertet werden.

argv:
echo\0

Der Name des lauffhigen Programms sei options. Das


Programm testet, ob es mit einer der Optionen help, -name
oder verbose aufgerufen wurde.

Hallo\0
Kurs\0
Programmentwicklung\0
2\0

options/options.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void usage(char *progname)
{
printf("Usage:\n");
printf("%s -help -name <your_name> -verbose\n\n",progname);
}
int main(int argc, char *argv[])
{
int i=1;
int flag_h=0, flag_n=0,flag_v=0;
char name[10]={'\0'};
if (argc < 2)
{
usage(argv[0]);
exit(1);
}
while (i<argc)
{
if(strcmp(argv[i],"-help") == 0)
{
flag_h = 1;
}
else if(strcmp(argv[i],"-verbose") == 0)
{
flag_v = 1;
}
else if(strcmp(argv[i],"-name")==0)

Prof. Dr. U. Matecki

Seite 147

Prof. Dr. U. Matecki

Seite 148

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

}
else if(strcmp(argv[i],"-name")==0)
{
flag_n = 1;
i++;
strncpy(name,argv[i],9);
}
else
{
usage(argv[0]);
}
i++;

2.2.5 Speicherverwaltung 4: Modellierung


mehrdimensionaler Arrays mit Zeigern
Bekannt aus Abschnitt 2.1.3: Statisch allokierte, mehrdimensionale Arrays oder mehrdimensionale VLAs.

Problem: Oft stellt sich die bentigte Gre z. B. einer N x MMatrix erst zur Laufzeit, beispielsweise nach einer Benutzereingabe heraus.

if(!flag_h)
{
printf("Option -help hat gefehlt\n");
}

Lsung: Mehrdimensionale Felder knnen ber Mehrfachverzeigerungen mit dynamischer Speicherallokation modelliert
werden.

if(!flag_v)
{
printf("Option -verbose hat gefehlt\n");
}

Beispiel: Wir modellieren eine N x M Matrix, bestehend aus


short-Werten, fr die zur Laufzeit die bentigte Anzahl Zeilen
und Spalten vom Benutzer abgefragt wird.
Die Matrix wird anschliessend "schachbrettartig" mit den Zahlen
0 und 255 gefllt und mit printf-Aufrufen ausgegeben.

if(!flag_n)
{
printf("Option -name hat gefehlt\n");
}
else
{
printf("Name war %s\n",name);
}
return 0;

Modellierung der Matrix:


Schritt 1: Gewnschte Form der Matrix feststellen:
Beispiel 5x7-Matrix:

Beispiel 5x5-Matrix:

Fr jede der Optionen help, -name und verbose wurde ein


int-Flag eingerichtet. Falls die Option per Kommandozeile gesetzt wurde, so wird dieses Flag gesetzt.
Spter kann dann anhand der Flags ausgewertet werden, ob
alle bentigten Parameter ber die Kommandozeile bergeben
wurden.

Prof. Dr. U. Matecki

Seite 149

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

Prof. Dr. U. Matecki

Seite 150

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Schritt 2: Wahl und Allokation der Datenstrukturen:


Wir wissen: ein z. B. short-Array der Gre m wird folgendermassen dynamisch allokiert:

short *vektor;
...
if((vektor =
(short *)malloc(m
*sizeof(short)))==NULL)
{
/* Fehlerbehandlung ... */;
}

und stellt Platz fr m short-Werte bereit. Die Variable vektor


zeigt nach dem malloc()-Aufruf auf das erste short-Element
des reservierten Speicherbereiches:

for(j=0;j<m;j++)
{
if( j%2 == 0)
{
*(vektor +j) =255;
}
else
{
*(vektor +j) =0;
}
}

Ein einzelnes Array dieser Art kann als Zeile einer short-Matrix aufgefat werden.
Die short-Matrix kann nun als Zeiger modelliert werden, der
auf ein Feld von n "Zeilen-Zeigern" der Gre m zeigt:
Beispiel: n=5 Vektoren der Gre m=7:
Platz fr m=7 short-Werte (m=7 Spalten)

Beispiel Vektor der Gre m=7:

255

255

255

255

Platz fr n=5
short-Zeiger
(n=5 Zeilen)

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

255

vektor

Dieses Array wird nach der Allokation in unserem Beispiel gefllt, indem die Werte an Speicherpltzen mit ungeradzahligen
Indizes auf 255 gesetzt werden, die anderen Werte aber auf 0:

Prof. Dr. U. Matecki

Seite 151

Prof. Dr. U. Matecki

Seite 152

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Schritt 2.1: Allokation von Speicherplatz fr n Zeigern auf


short-Zeiger (Zeilenrichtung, von oben nach unten zu lesen):

Schritt 2.2: Allokation von Speicherplatz fr die n einzelnen


Zeilen der Gre m:

short **matrix;
int i,j, k;
if((matrix = (short **)malloc(n *sizeof(short*)))==NULL)
{
/*Fehlerbehandlung, wenn kein Speicher mehr verfuegbar */;
}

matrix ist nach der


Allokation ein Zeiger
auf n short-Zeiger

for (i=0;i < n;i++)


{
if((*(matrix+i) = (short *)malloc( m *sizeof(short)))==NULL)
{
/*Fehlerbehandlung, falls kein Speicher verfuegbar*/;
}
}

Der Zeiger an Stelle i


zeigt auf einen zusammenhngenden Bereich von m
short - Werten

Platz fr n short-Zeiger

Platz fr m short-Werte

Jeder Schritt der for()-Schleife liefert nun Platz fr eine Zeile


der Gre m:
liefert:
Platz fr m = 7 short-Werte

Platz fr n=5
short-Zeiger
(n=5 Zeilen)

i = 0  matrix + 0

Die n Zeiger sind noch nicht


initialisiert, d. h. fr den Inhalt
der einzelnen Zeilen wurde
noch kein Speicherplatz
allokiert.

i = 1  matrix + 1
i = 2  matrix + 2
...

i = n-1

Prof. Dr. U. Matecki

Seite 153

Prof. Dr. U. Matecki

Seite 154

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Schritt 3: Fllen der Matrix mit schachbrettartig angeordneten


Werten:
Die bis jetzt allokierte n x m-Matrix enthlt noch keine von uns
definierten Werte, sondern irgendwelche Werte, da sie noch
nicht initialisiert ist. Wir fllen sie nun mit den schachbrettartig
angeordneten Werten 0 und 255:

for (i = 0; i < n; i++)


{
for(j = 0; j < m; j++)
{

Speicheradresse matrix + i:

255

255

255

255

255

*(matrix + i) ist Inhalt an


Speicheradresse matrix + i
und damit Anfangsadresse von
Zeile i

ber alle Zeilen i


ber alle Spalten j von Zeile i

if((i%2 == 1 && j%2 == 1) || (i%2 == 0 && j%2 == 0))


{
*(*(matrix+i)+j)=255;
}
else
{
*(*(matrix+i)+j)=0;
}
}

Speicheradresse *(matrix + i)+j


*(*(matrix + i)+j) ist Inhalt
an Speicheradresse *(matrix + i)+j
und damit 0

Die if()-Abfrage innerhalb der 2 verschachtelten for()Schleifen ist folgendermaen zu lesen:

if ((Zeile und
oder
(Zeile und
{
setze Wert
}
else
{
setze Wert
}

Der Ausdruck
*(*(matrix+i)+j)
ist z. B. fr i=1 und j=2 folgendermaen zu lesen:

Spalte beide gerade)


Spalte beide ungerade))
an dieser Stelle auf 255;

an dieser Stelle auf 0;

Damit ist unsere Schachbrettmatrix allokiert und mit Werten


gefllt.

Prof. Dr. U. Matecki

Seite 155

Prof. Dr. U. Matecki

Seite 156

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Zusammenfassung: Wir haben

2.3 Zusammengesetzte Datenstrukturen Teil II

 in Schritt 1 festgelegt, wie unsere Matrix spter aussehen


soll,
 in Schritt 2 den fr unsere Matrix bentigten Speicher allokiert und
 in Schritt 3 die neu allokierte Matrix mit Werten gefllt.

2.3.1 Strukturen 1 - Vereinbarung


Die einzige bisher in C kennengelernte, zusammengesetzte
Datenstruktur war das Feld bzw. Array. Weiterhin haben wir einige Speicherverwaltungs-Mechanismen kennengelernt, die es
uns ermglichen, Arrays u. . zur Laufzeit zu allokieren. Auf
diese Weise sind Programmierende nicht mehr an feste Gren
vor Programmstart gebunden, sondern sie knnen das Programm zur Laufzeit entscheiden lassen, wie gro seine Felder
werden mssen.
Nun wre es noch wnschenswert, Datentypen vereinbaren zu
knnen, die es ermglichen, mehrere Eintrge mglicherweise
verschiedenen Datentyps entgegenzunehmen. Beispiel: Wir
wollen
-

Vornamen,
Nachnamen,
Matrikelnr.,
Semester und
Frderungs-Hchstdauer (in Semestern)

von Studierenden in einer Datenstruktur "studierende" halten.


Die in C hierfr zur Verfgung gestellte Datenstruktur heit
"Struktur" (engl. struct) und wird folgendermaen vereinbart:

Prof. Dr. U. Matecki

Seite 157

Prof. Dr. U. Matecki

Seite 158

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;

Wie greifen wir nun auf die einzelnen Attribute von z. B. s1 zu?
 .-Operator (hnlich wie der Zugriff auf Methoden von
Objekten in Java oder der Zugriff auf Attribute von Objekten in
Java)

} s1, s2, s3;


 Das in C reservierte Schlsselwort struct steht am Anfang einer Strukturvereinbarung. Anschlieend kann ein
sog. Etikett (engl. structure tag) folgen, welches spter als
Abkrzung verwendet wird (bei uns: studierende).

s1.vorname = (char*)malloc(MAXNAME*sizeof(char));
s1.nachname = (char*)malloc(MAXNAME*sizeof(char));
s1.matrikel = 4711;
s1.semester = 5;
s1.foerder = MAXFOERDER-s1.semester;

 In den geschweiften Klammern folgen nun die Attribute mit


ihren Datentypen, aus denen sich unsere Struktur zusammensetzt.
 Nach der geschlossenen geschweiften Klammer } knnen
noch ein oder mehrere Variablennamen folgen (bei uns:
s1, s2, s3). Sie sorgen im obigen Beispiel dafr, da 3
Strukturen s1, s2 und s3 angelegt werden. Fr sie wird
wirklich Speicher reserviert.
Eine quivalente Vereinbarung sieht folgendermaen aus:
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
};
struct studierende s1, s2, s3;

Prof. Dr. U. Matecki

Seite 159

Prof. Dr. U. Matecki

Seite 160

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

ACHTUNG: hnlich wie bei der Vereinbarung eines Feldes ist


der bei der Vereinbarung einer Struktur-Variablen reservierte
Speicherbereich zusammenhngend.

Hier ist eine Struktur gleichen Inhalts in Platz sparenderer Form


zu sehen:

struct meinestruktur
{
char cwert;
int iwert1;
int iwert2;
long lwert1;
char cwert2;
} s1;

struct meinestruktur
{
char cwert;
char cwert2;
int iwert1;
int iwert2;
long lwert1;
} s1;

Startadresse von s1 sei z. B. Byte 4800010

Startadresse von s1 sei z. B. Byte 4800010

Wortbreite des Rechners sei 4 Byte

Wortbreite des Rechners sei 4 Byte


aufgefllt

aufgefllt
Startadresse cwert1 mit direkt dahinter liegendem
cwert2: 48000, Gre cwert1 und cwert2: je 1 Byte

Startadresse cwert1: 48000, Gre cwert1: 1 Byte


Startadresse iwert1: 48004, Gre iwert1: 4 Byte

Startadresse iwert1: 48004, Gre iwert1: 4 Byte

Startadresse iwert2: 48008, Gre iwert2: 4 Byte

Startadresse iwert2: 48008, Gre iwert2: 4 Byte

Startadresse lwert1: 48012, Gre lwert1: 4 Byte


Startadresse cwert2: 48016, Gre cwert2: 1 Byte

Startadresse lwert1: 48012, Gre lwert1: 4 Byte


Gesamtgre der Struktur: 16 Byte, da bei beiden char-Werten aufgefllt.

aufgefllt
Gesamtgre der Struktur: 20 Byte, da bei beiden char-Werten aufgefllt.

Bei Attributen, deren Gre kleiner ist, als die Wortbreite des
Rechners, wird "aufgefllt", so da die Gesamtgre der Struktur immer ein Vielfaches der Wortbreite des Rechners ist. (Abzuprfen mit sizeof()). Die Gre der einzelnen Attribute einer
Struktur entspricht jedoch weiterhin der Gre des jeweiligen
Datentyps.

Prof. Dr. U. Matecki

Seite 161

Das folgende Beispiel zeigt die Vereinbarung einer Struktur,


den Zugriff auf ihre Komponenten, sowie die Zuweisung an eine
andere Struktur.

Prof. Dr. U. Matecki

Seite 162

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

struct1/struct1.c:

s3 = s1;
printf("s3 = {\n%s;\n%s;\n%d;\n%d;\n%d;\n};\n",
s3.vorname,
s3.nachname,
s3.matrikel,
s3.semester,
s3.foerder);

#include <stdio.h>
#define FOERDERHOECHST 10
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
} s1;

printf("sizeof(s3): %d\n",sizeof(s3));
/* Achtung: Unsere Speicher-Adresse in vorname hat
* nur 4 Byte!!!
*/
printf("sizeof(s3.vorname): %d\n",sizeof(s3.vorname));
/* Achtung: Unsere Speicher-Adresse in nachname hat
* nur 4 Byte!!!
*/
printf("sizeof(s3.nachname): %d\n",sizeof(s3.nachname));
printf("sizeof(s3.matrikel): %d\n",sizeof(s3.matrikel));
printf("sizeof(s3.semester): %d\n",sizeof(s3.semester));
printf("sizeof(s3.foerder): %d\n",sizeof(s3.foerder));

int main()
{
struct studierende s3;
/* ACHTUNG: vorname zeigt auf einen lokalen Speicherbereich!!! */
s1.vorname = "Alfons";

return 0;

/* ACHTUNG: nachname zeigt auf einen lokalen Speicherbereich!!! */


s1.nachname = "Ruebezahl";
s1.matrikel = 4711;
s1.semester = 6;
s1.foerder = FOERDERHOECHST - s1.semester;

Prof. Dr. U. Matecki

Seite 163

Prof. Dr. U. Matecki

Seite 164

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.3.2 Strukturen 2 bergabe an Funktionen


Die Attribute einer Struktur werden i. d. R. wiederholt gefllt,
ausgewertet und wieder berschrieben. Strukturen knnen
daher an Funktionen bergeben werden und auch von Funktionen zurckgegeben werden.
ACHTUNG: C kennt nur call-by-value. Bei der bergabe einer
Struktur als Parameter wird also eine Kopie angefertigt, die auf
dem Stack der Funktion abgelegt wird. Dies kann sehr laufzeitund speicherineffizient sein, wenn die Funktion in einer Schleife
aufgerufen wird.

struct2/struct2Funcs.h
#ifndef STRUCT2FUNCS_H
#define STRUCT2FUNCS_H
#define FOERDERHOECHST 10
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
};
struct studierende setzeVorname(struct studierende s,
char *vorname);

Beispiel 1:
Wir definieren unsere Studierenden-Struktur, sowie 3 Funktionen: Eine Funktion, die eine initialisierte Struktur zurckgibt,
sowie 2 Funktionen, die eine Struktur und eine Zeichenkette
bergeben bekommen. Beide geben eine neue (Kopie) der
bergebenen Struktur zurck, bei der nun eines ihrer Attribute
mit einer Kopie der bergebenen Zeichenkette gesetzt wurde.

#endif
struct2_funcs.c:
#include
#include
#include
#include

<stdio.h>
<stdlib.h>
<string.h>
"struct2Funcs.h"

struct studierende setzeVorname(struct studierende s,


char *vorname)
{
int length = strlen(vorname) + 1;
s.vorname = (char *)malloc(length * sizeof(char));
if (s.vorname != NULL)
{
strcpy(s.vorname, vorname);
}
return s;
}

Prof. Dr. U. Matecki

Seite 165

Prof. Dr. U. Matecki

Seite 166

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.3.3 Strukturen 3 Arrays aus Strukturen


und Zeiger auf Strukturen

struct2.c
#include <stdio.h>
#include "struct2Funcs.h"
int main()
{
struct studierende s,t;

2.3.3.1 Arrays aus Strukturen


Strukturen werden hufig fr die Implementation von Tabellen
verwendet, wie wir sie z. B. aus Excel kennen:

s.matrikel = 4711;
s.semester = 5;
s.foerder = FOERDERHOECHST-s.semester;
s.nachname = "Helmchen";
t = setzeVorname(s, "Hugo");
printf("t = {\n%s;\n%s;\n%d;\n%d;\n%d;\n};\n",
t.vorname,
t.nachname,
t.matrikel,
t.semester,
t.foerder);

Dies entspricht einem Feld aus Strukturen in C. Eine Zeile der


Tabelle entspricht einem Feldelement:
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;

return 0;
}

};
struct studierende s[2];

Prof. Dr. U. Matecki

Seite 167

s[0] :

s[1] :

Alfons

Eusebia

Ruebezahl

Wackelzahn

4711

108

55

Prof. Dr. U. Matecki

Seite 168

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Eine Ausprgung einer Struktur entspricht hierbei einer Tabellenzeile. Um eine n-zeilige Tabelle zu modellieren, wird ein
n-elementiges Array einer Struktur erzeugt.

2.3.3.2 Zeiger auf Strukturen

ACHTUNG: Die oben gezeigte Konstruktion hat einen gravierenden Nachteil:


Wenn Sie die Datenstze im Array sortieren mchten, so mssen Sie die Strukturen selbst umspeichern. Dies ist zwar mglich, aber bezglich der Laufzeit sehr ineffizient.
Lsung:  nchste Seite!

Bisher haben wir ganze Strukturen als "Kopien" per call-byvalue mit Funktionen verarbeitet. Da bei diesem Vorgang jedoch jedesmal eine Kopie der gerade bearbeiteten Struktur
vom System angefertigt wird, ist dieser Mechanismus hufig zu
laufzeitaufwendig.
Meistens wird daher mit Funktionen gearbeitet, die Zeiger auf
Strukturen hereingereicht bekommen und auch Zeiger auf
Strukturen wieder herausreichen.
Auch bei den vorher beschriebenen Tabellenkonstruktionen ist
es, sobald z. B. Sortierverfahren angewendet werden, weniger
laufzeitaufwendig, wenn nur Zeiger auf Strukturen "umgebogen"
werden und nicht ganze Strukturen immer wieder umgespeichert werden.
 Vereinbarung eines Zeigers auf eine Struktur:
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
};
struct studierende r, s, *t;
...
t = &s;

Prof. Dr. U. Matecki

Seite 169

Prof. Dr. U. Matecki

Seite 170

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Zugriff auf die Komponenten der Struktur, auf die t zeigt:


r.matrikel = (*t).matrikel;
Die Klammern bei (*t).matrikel sind notwendig, da
der .-Operator Vorrang vor dem Inhaltsoperator * hat.
Hierzu gibt es in C eine Abkrzung: den -> - Operator:
r.matrikel = t->matrikel;
Diese Abkrzung sollte unbedingt genutzt werden!!!

 Zeigerarithmetik bei Zeigern auf Strukturen:


Da der .-Operator und der ->-Operator sehr hohen Vorrang haben, ist bei der Zeigerarithmetik in diesem Kapitel
einiges zu beachten. Dies wollen wir an einigen Beispielen
illustrieren:
struct studierende *t;
int x;
char *str;
char c;
............
/*inkrementiert semester, da -> Vorrang hat
*/
++t->semester;
/* inkrementiert t, da () hoechste
* Prioritaet hat
*/
(++t)->semester;

/* greift auf das Objekt zu, auf das


* vorname zeigt (also erster Buchstabe
*aus str)
*/
c = *t->vorname;
/* Inkrementiert vorname nach dem Zugriff
* auf das erste Zeichen
*/
*t->vorname++;

/* Inkrementiert das Zeichen, auf das


* Vorname zeigt
*/
(*t->vorname)++;
Prof. Dr. U. Matecki

Seite 171

Prof. Dr. U. Matecki

Seite 172

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Das nchste Programm ist eine kleine Tabellenanwendung. Wir


arbeiten jedoch nun mit einem Feld, bestehend aus Zeigern auf
Strukturen. Die Datenstze werden wie bisher ber die Konsole
eingelesen, z. B in der folgenden Reihenfolge:

In der folgenden Abbildung werden die alten Zeiger durch gestrichelte Pfeile markiert, die umgesetzten durch farbige, dick
unterlegte Pfeile:
Alois
Ruebezahl
399
...

Alois
Ruebezahl
399
...

Eusebia
Wackelzahn
123
...

Eusebia
Wackelzahn
123
...

Erika
Mustermann
220
...

Erika
Mustermann
220
...

Fred
Feuerstein
5
...

Fred
Feuerstein
5
...

Hans
Mller
12
...

Hans
Mller
12
...

Anschlieend werden sie sortiert nach Matrikelnr. und in aufsteigender Reihenfolge ausgegeben.
ACHTUNG: Die Sortierung erfolgt nicht durch Umspeichern der
Strukturen, sondern nur durch Umspeichern der Zeiger auf die
Strukturen.

Prof. Dr. U. Matecki

Seite 173

Prof. Dr. U. Matecki

Seite 174

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

struct3/struct3Funcs.h:

struct3/struct3Funcs.c

#ifndef STRUCT3FUNCS_H
#define STRUCT3FUNCS_H

#include <stdio.h>
#include <stdlib.h>
#include "struct3Funcs.h"

#define FOERDERHOECHST 10
#define MAXNAME 100
struct studierende {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
};
struct studierende *liesDatensatz ();
void druckeDatensatz(struct studierende *s);
void freeStrings(struct studierende *s);
void raeumeAuf(struct studierende *s[], int anzahl);
void sort (struct studierende *s[], int anzahl);
void swap(struct studierende *s[], int i, int j);
#endif

/* - allokiert Speicher fuer eine Studierenden-Struktur


* - fuellt die Struktur mit scanf()
* - liefert Zeiger auf die Struktur zurueck
* - Falls Allokation erfolglos: liefert NULL-Pointer
*/
struct studierende * liesDatensatz()
{
struct studierende *s;
if ((s = (struct studierende *)malloc (
sizeof(struct studierende)))
==NULL)
{
return NULL;
}
s->vorname = (char*)malloc(MAXNAME * sizeof(char));
if (s->vorname != NULL)
{
printf("\nvorname: ");
scanf("%s",s->vorname);
}
s->nachname = (char*)malloc(MAXNAME * sizeof(char));
if (s->nachname != NULL)
{
printf("\nnachname: ");
scanf("%s",s->nachname);
}
printf("\nmatrikel: ");
scanf("%d", &s->matrikel);
printf("\nsemester: ");
scanf("%d", &s->semester);

Prof. Dr. U. Matecki

Seite 175

Prof. Dr. U. Matecki

Seite 176

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

printf("\nfoerder: ");
scanf("%d", &s->foerder);

/* gibt den Speicher der Strukturen frei, auf die s[0]


* bis s[anzahl-1] zeigen
*/
void raeumeAuf(struct studierende *s[], int anzahl)
{
int i;

return s;
}
/* gibt mit printf die Elemente der Struktur aus, auf
* die s zeigt.
*/
void druckeDatensatz(struct studierende *s)
{
printf("\n");
printf("vorname:%s\n",s->vorname);
printf("nachname:%s\n",s->nachname);
printf("matrikel:%d\n",s->matrikel);
printf("semester:%d\n",s->semester);
printf("foerder:%d\n",s->foerder);
printf("------------------------\n");
}
/* gibt den Speicher der vorname + nachname-Komponenten
* der Struktur frei, auf die s zeigt.
*/
void freeStrings(struct studierende *s)
{
if (s->vorname != NULL)
{
free(s->vorname);
}
if (s->nachname != NULL)
{
free(s->nachname);
}
}

Prof. Dr. U. Matecki

Seite 177

for (i=0; i<anzahl;i++)


{
freeStrings(s[i]);
free (s[i]);
}
}

/* Sortieren durch Zeiger-umbiegen. Hier werden keine


* Strukturen umgespeichert, sondern nur Adressen!!!
* Algorithmus: Bubblesort
*/
void sort (struct studierende *s[], int anzahl)
{
int i, j;
for(i=0;i<anzahl;i++)
{
for(j=0;j<anzahl;j++)
{
if(s[i]->matrikel < s[j]->matrikel)
{
swap(s,i,j);
}
}
}
}

Prof. Dr. U. Matecki

Seite 178

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

/*
* Tauscht 2 Strukturzeiger Nr. i und j im Zeigerarray
*/
void swap(struct studierende *s[], int i, int j)
{
struct studierende *help;
help = s[i];
s[i] = s[j];
s[j] = help;
}

/* Eigentlich muss hier noch ueberprueft werden, ob argv[1]


* auch wirklich eine Ziffernkette ist. Denken Sie sich
* hierfuer eine geeignete Funktion aus!
*/
anzahlDatensaetze = atoi(argv[1]);

struct3/struct3.c:

printf("anzahlDatensaetze %d\n",anzahlDatensaetze);

#include <stdio.h>
#include <stdlib.h>
#include "struct3Funcs.h"
/*Gebrauchsanweisung fuer Aufruf des Programms */
void usage(char *progname)
{
printf("Usage:\n");
printf("%s <Anzahl Datensaetze>",progname);
}
/* main(): - allokiert Feld fuer n Datensaetze
*
- liest die Datensaetze mit liesDatensatz() ein
*
- sortiert das Feld mit sort() anhand der
*
Matrikelnummer
*
- gibt die sortierte Liste mit druckeDatensatz()
*
aus
*/
int main(int argc, char *argv[])
{
struct studierende **s;
int anzahlDatensaetze=0;
int i;

Prof. Dr. U. Matecki

/* Wurde ein Kommandozeilenargument mitgegeben? */


if(argc != 2)
{
usage(argv[0]);
exit(1);
}

Seite 179

/* Feld fuer die Datenstruktur-Zeiger allokieren */


s = (struct studierende **)malloc(
anzahlDatensaetze*sizeof(struct studierende*));
if(s == NULL)
{
printf("Kein Speicher mehr!!\n");
exit(1);
}
/* Datensaetze mit allokieren + mit scanf() einlesen;
*/
for (i=0;i<anzahlDatensaetze;i++)
{
if((s[i] = liesDatensatz()) == NULL)
{
printf("Kein Speicher zum Einlesen mehr!!\n");
raeumeAuf(s,i);
free(s);
exit(1);
}
}

Prof. Dr. U. Matecki

Seite 180

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.3.4 Vereinbarung von Strukturen mit


typedef

/*Datensaetze nach Matrikel sortieren */


sort(s,anzahlDatensaetze);

Eine selbst definierte Datenstruktur oder andere Datentypen


knnen auch als eigener Datentyp betrachtet und vereinbart
werden, hnlich wie in Java die Definition einer neuen Klasse.

/* Datensaetze mit printf() ausgeben */


for (i=0;i<anzahlDatensaetze;i++)
{
druckeDatensatz(s[i]);
}

In C gibt es hierfr ein eigenes Schlsselwort, mit dem neue


Typnamen vereinbart werden knnen: typedef
Einfaches Beispiel :

/* dynamisch allokierte Zeichenketten-Komponenten


* der Strukturen wieder freigeben;
*/
raeumeAuf(s,anzahlDatensaetze);
free (s);
return 0;
}

typedef char *String;


typedef int Length;
macht den Namen "String" synonym zu char* und den
Namen "Length" synonym zu int.
Beide Typen knnen nun wie jeder andere Datentyp bei der
Vereinbarung von Variablen, Funktionsprototypen, usw. genutzt
werden:
String myStr;
Length myLen;
int stringcopy(String s1, Strings2);
Hufiger eingesetzt wird typedef jedoch bei der Vereinbarung
von komplizierteren Datentypen:
typedef struct stud Studierende;
typedef struct stud {
char *vorname;
char *nachname;
int matrikel;
int semester;
int foerder;
};

Prof. Dr. U. Matecki

Seite 181

Prof. Dr. U. Matecki

Seite 182

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Anschlieend knnen Variablen dieser Struktur folgendermaen vereinbart werden:

2.4 Varianten (Unions)

Studierende s;
 Das Schlsselwort struct kann jetzt weggelassen werden.
ACHTUNG: Das Schlsselwort typedef konstruiert KEINEN
neuen Datentyp. Es sorgt lediglich dafr, da komplizierte Vereinbarungen spter mit einem vereinfachten Namen verwendet
werden knnen.

In C gibt es die Mglichkeit, Variablen zu vereinbaren, die mehrere Mglichkeiten (Varianten) von Datentypen enthalten, von
denen eine ausgewhlt wird.
Auf diese Weise knnen verschiedene Arten von Datenobjekten
in ein und demselben Speicherbereich manipuliert werden (allerdings immer nur eine zu einem Zeitpunkt).
Einfaches Beispiel:
union wasBinIch {
int iVal;
float fVal;
char cVal;
} w;
int type;
...
void setzeUnion(int typ)
{
type = typ;
switch(type)
{
case INT: scanf("%d",&w.iVal);
break;
case FL: scanf("%f",&w.fVal);
break;
case CH: w.cVal=getchar();
break;
}

void printUnion()
Prof. Dr. U. Matecki

Seite 183

Prof. Dr. U. Matecki

Seite 184

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

{
switch(type)
{
case INT: printf("%d\n",w.iVal);
break;
case FL: printf("%f\n",w.fVal);
break;
case CH: printf("%c\n",w.cVal);
break;
default: break;
}
}
Die oben vereinbarte Variable w ist so gro, da sie den grten der drei Datentypen aufnehmen kann.
 nicht zu verwechseln mit Strukturen!!! Diese sind gro genug, um ALLE ihre Komponenten aufnehmen zu knnen.

personenTabelle[0].vorname = holeVorname();
personenTabelle[0].nachname = holeNachname();
personenTabelle[0].nr.matrikel = 12345;
Die Lehrende unseres Semesters knnte folgendermaen eingetragen werden:
personenTabelle[59].vorname = holeVorname();
personenTabelle[59].nachname = holeNachname();
personenTabelle[59].nr.l.persNrVerwaltung =
4711;
personenTabelle[59].nr.l.persNrKrankenVers =
4712;

Es mu immer darauf geachtet werden, da der zuletzt gesetzte Datentyp entnommen wird. blicherweise bewerkstelligt
man dies, indem eine globale Variable mitgefhrt wird, in der
festgehalten wird, welcher Typ zuletzt gespeichert wurde.
Unions knnen innerhalb von Strukturen auftreten und umgekehrt:
struct persFH {
char *vorname;
char *nachname;
union {
int matrikel;
struct lehrender {
int persNrVerwaltung;
int persNrKrankenVers;
} l;
} nr;
} personenTabelle[60];
Ein/e Studierende/r unseres Semesters knnte jetzt z. B. folgendermaen in unsere Personentabelle eingetragen werden:
Prof. Dr. U. Matecki
Seite 185

Prof. Dr. U. Matecki

Seite 186

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.5 Aufzhlungen (enumeration)

2.6 Dateien in C

Aufzhlungen sind in C eine effiziente Mglichkeit, Konstanten


zu vereinbaren.
Wenn man beispielsweise das Vorkommen verschiedener
Fahrzeug-Farben in einer Fahrzeugverwaltung eines Autohndlers kodieren will, kann dies in C folgendermaen geschehen:

Bisher: Daten die unsere Programme verarbeitet haben, wurden als Konstanten vereinbart oder ber die Tastatur eingegeben.
Die Ausgabe der Daten erfolgte ausschliesslich auf den Bildschirm.
Jetzt: Die Daten, die unsere Programme verarbeiten, werden
von Datei eingelesen. Die Ergebnisse unserer Programme werden auf Datei permanent auf der Festplatte gespeichert.

enum Farbe { rot, weiss, silber, blau};


Hierdurch wird es uns ermglicht, mit Namen im Quellcode auf
die verschiedenen Farben zuzugreifen, ohne da fr jede Farbe
ein eigener #define oder eine eigene Konstante vereinbart
werden mu.
Die Vereinbarung einer Variablen dieses Typs erfolgt anschlieend mit
enum Farbe f;
f = ferrarirot;
Die einzelnen Werte eines Aufzhlungstyps sind implizit vom
Typ int und werden, falls nichts anderes angegeben wird, von
0 bis n-1 in Reihenfolge ihrer Vereinbarung durchnumeriert.

Die Daten, welche ein C-Programm aus einer Datei lesen/auf


eine Datei speichern kann, knnen auf 2 Arten interpretiert
werden:
Zeichenorientierte
Interpretation
Die Daten in dieser
Datei werden
zeichenorientiert
gelesen: Die Zahl
1.65 besteht hier
aus 4 ASCII-Zeichen.

Byteorientierte Interpretation

000100110011100111...

Die Zahl 1.65 ist hier in


4 Bytes in Fliekommanotation gespeichert.

Im obigen Beispiel htte ferrarirot also den Wert 0 und nachtblau den Wert 3.
Wenn eine "Nicht-Standard"-Nummerierung gewnscht ist, wird
dies folgendermaen vereinbart:
enum Farbe { ferrarirot = 2, vanillewei = 3,
silberpfeilsilber = 4,
nachtblau = 5};

Prof. Dr. U. Matecki

Seite 187

ACHTUNG: Eine Datei ist immer eine Ansammlung von Bytes


auf der Festplatte (vorlufig, bis wir mehr darber erfahren),
sie knnen jedoch auf diese 2 Arten interpretiert werden!!

Prof. Dr. U. Matecki

Seite 188

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.6.1 Der FILE-Pointer ffnen und


Schlieen von Dateien
 C stellt eine besondere Datenstruktur fr Lese- und
Schreibvorgnge auf/von Dateien zur Verfgung: Die Datenstruktur FILE. Hierbei handelt es sich tatschlich um
eine Struktur (struct) im Sinne von C. Wir kmmern uns
allerdings nicht darum, wie die konkreten Komponenten
dieser Struktur aussehen. Fr uns gengt es, zu wissen,
da diese Struktur, wenn sie von speziellen C-Funktionen
eingerichtet wurde, alle Informationen ber die Datei, mit
der wir arbeiten wollen, enthlt.
Die File-I/O-Funktionen, die wir im Rahmen dieser Vorlesung kennenlernen, geben alle entweder einen Zeiger auf
eine FILE-Datenstruktur (kurz: FILE-Pointer) zurck, oder
sie nehmen einen gltigen FILE-Pointer entgegen.
 Ein FILE-Pointer wird vereinbart, wie jeder andere Zeiger
auf eine Struktur auch:
FILE *fp;
 Bevor auf den Inhalt einer Datei zugegriffen werden kann,
mu sie erst geffnet werden. Dies geschieht mit der
Funktion
FILE *fopen (char *name, char *mode);
Die Funktion bekommt als ersten Parameter einen Dateinamen geliefert, als zweiten Parameter eine Zeichenkette,
die die Zugriffs-Art auf die Datei spezifiziert. Erlaubt sind
hier (u. a.)

o "r" zum Lesen ffnen


o "w" zum Schreiben ffnen; ggf. alten Inhalt
wegwerfen.
o "a" Was neu auf die Datei geschrieben wird, wird
an den bisherigen Inhalt angehngt
o "r+" zum ndern ffnen (Lesen + Schreiben)
o "w+" zum ndern erzeugen; ggf. alten Inhalt wegwerfen.
o "a+" Was neu auf die Datei geschrieben wird, wird
an den bisherigen Inhalt angehngt. Es darf gelesen
und geschrieben werden.
o "rb" Datei zum Lesen geffnet und binr zu interpretieren.
o "wb" Datei zum Schreiben geffnet und binr zu
interbretieren
o "ab" Was neu auf die Datei geschrieben wird, wird
an den bisherigen Inhalt angehngt. Die Datei wird
binr interpretiert.
Die Funktion liefert einen FILE-Pointer zurck. Falls die
Datei nicht geffnet werden konnte, wird ein NULL-Pointer
zurckgeliefert (vgl. malloc()).
Bei "r+", "w+" und "a+" heit ndern, da auf dem
gleichen FILE-Pointer gelesen und geschrieben werden
darf.
Auf diesem FILE-Pointer werden spter alle Lese- und
Schreibvorgnge ausgefhrt.

Prof. Dr. U. Matecki

Seite 189

Prof. Dr. U. Matecki

Seite 190

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

 Nach Gebrauch wird eine Datei wieder geschlossen. Dies


geschieht mit der Funktion
void fclose(FILE *fp);
Sie bekommt als Parameter den FILE-Pointer, der vorher
von fopen() zurckgeliefert wurde.
ACHTUNG:
Nach dem Aufruf dieser Funktion kann auf dem FILEPointer, der geschlossen wurde, keine Lese- oder
Schreiboperation mehr ausgefhrt werden. Der Versuch,
dies zu tun kann zu Programmabstrzen fhren!!

2.6.2 Datei Ein-/Ausgabe


2.6.2.1 1. Allgemeines zu Lese- und Schreibvorgngen auf Dateien
Man kann sich die FILE-Struktur als eine Struktur vorstellen, die
einen Zeiger enthlt, der sich merkt, bis wohin auf eine Datei
lesenderweise oder schreibenderweise zugegriffen wurde 
Die Datei wird behandelt wie ein "Strom" aus Daten, der an einem gedachten Zeiger entlangluft.
Dateien als Datenstrme:

 Es gibt in C 3 spezielle, vordefinierte FILE-Pointer von


"Dateien" (besser: Datenstrmen), die immer geffnet
sind:

o stdin reprsentiert die Standard-Eingabe (Tastatur). Von ihr haben wir z. B. mit getchar() oder scanf()
gelesen.
o stdout reprsentiert die Standard-Ausgabe (Bildschirm). Auf sie haben wir schon mit putchar() oder
printf() geschrieben.
o stderr reprsentiert die Standard-Fehlerausgabe. Alles was hierauf ausgegeben wird, erscheint
auf dem Bildschirm, kann aber nicht auf eine andere
Datei umgelenkt werden.
 Tastatur und Bildschirm werden in C wie Dateien behandelt!

Prof. Dr. U. Matecki

Seite 191

Nach dem ffnen,


vor dem ersten Leseoder Schreibzugriff

z. B. 4 Bytes
eingelesen
nach dem ersten
Lese- oder Schreibzugriff

z. B. 3 Bytes
eingelesen
nach dem nchsten
Lese- oder Schreibzugriff

Prof. Dr. U. Matecki

Seite 192

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.6.2.2 Zeichenorientiertes Lesen und Schreiben

Beispiel: Ein Kopierprogramm:


file1/file1.c:

Bisher haben wir noch nicht viel mit unserer Testdatei getan.
Als nchstes wollen wir zunchst zeichenorientiert von einer
Datei lesen und auf eine andere Datei schreiben.
Analog zu getchar() und putchar() gibt es in C 2 Funktionen

void usage(char *progname)


{ printf("Usage:\n");
printf("%s <Eingabedatei> <Ausgabedatei>\n",progname);
}
int main(int argc, char *argv[])
{
char *dateiIn, *dateiOut;
FILE *fpIn, *fpOut;
int c;

int getc(FILE *fp);


und
int putc(int c, FILE *fp);
Die Funktion getc() tut das gleiche wie getchar(), nur da
hier von einer geffneten Datei gelesen wird. Die Funktion
putc() tut das gleiche wie putchar(), nur da hier auf eine
geffnete Datei geschrieben wird.
ACHTUNG: Auch wenn bei putc() das zu schreibende Zeichen als int bergeben wird: Auf die durch fp reprsentierte
Datei wird nur ein Zeichen also 1 Byte geschrieben!!!

Prof. Dr. U. Matecki

#include <stdio.h>

Seite 193

/* Wurde zwei Kommandozeilenargumente mitgegeben? */


if(argc != 3)
{
usage(argv[0]);
exit(1);
}
dateiIn = argv[1];
dateiOut = argv[2];
/* Eingabedatei zum Lesen oeffnen, Ausgabedatei zum Schreiben
* oeffnen
*/
if ((fpIn = fopen(dateiIn,"r"))==NULL)
{
printf("Eingabe-Datei %s nicht vorhanden\n",dateiIn);
exit(1);
}
if ((fpOut = fopen(dateiOut,"w"))==NULL)

Prof. Dr. U. Matecki

Seite 194

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

if ((fpOut = fopen(dateiOut,"w"))==NULL)
{
printf("Ausgabe-Datei %s kann nicht geoeffnet werden\n",
dateiOut);
exit(1);
}

fscanf(stdin, "%d",&wert);
und
scanf("%d",&wert);
sowie

/* Inhalt von Eingabedatei in Ausgabedatei kopieren.


* ACHTUNG: c ist ein 4-Byte-int. Trotzdem wird bei
* jedem getc() nur ein Zeichen (= 1 Byte)
* gelesen und bei putc() nur ein Zeichen (= 1 Byte)
* geschrieben !!!! Die Position des Dateizeigers kann
* mit ftell() in Bytes abgefragt werden.
*/
while ((c=getc(fpIn))!= EOF)
{
printf("Position: %d <%c>\n",ftell(fpIn),c);
putc(c, fpOut);
}

fprintf(stdout,"%d",wert);
und
printf(stdout,"%d",wert);
quivalent.
Ein Beispielprogramm:
file2/file2.c:
#include <stdio.h>

/* Dateien wieder schliessen */


fclose (fpIn);
fclose (fpOut);
return 0;

void usage(char *progname)


{
printf("Usage:\n");
printf("%s <Testdatei> \n",progname);
}

Fr die formatierte Ein- und Ausgabe von Dateien gibt es in C


die Funktionen
int fscanf(FILE *fp, char *formatstring,
argumente....);
und
int fprintf(FILE *fp, char *formatstring,
argumente ...);
Beide Funktionen arbeiten genauso wie scanf() und printf();
Tatschlich sind die Aufrufe
Prof. Dr. U. Matecki

Seite 195

int main(int argc, char *argv[])


{
char *datei;
FILE *fpIn, *fpOut;
int i=5,
j;
float f = 4.56,
g;
/* Wurde zwei Kommandozeilenargumente mitgegeben? */
if(argc != 2)
{
usage(argv[0]);
exit(1);
}

Prof. Dr. U. Matecki

Seite 196

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

}
datei = argv[1];

/* Eingelesene Werte auf die Standardausgabe (Bildschirm)


* ausgeben. Ist aequivalent zu printf()
*/
fprintf( stdout,
"Geschriebener Wert: %d, Gelesener Wert: %d\n",
i,j);
fprintf( stdout,
"Geschriebener Wert: %f, Gelesener Wert: %f\n",
f,g);

/* Ausgabedatei zum Schreiben


* oeffnen
*/
if ((fpOut = fopen(datei,"w"))==NULL)
{
printf("Ausgabe-Datei %s kann nicht geoeffnet werden\n",
datei);
exit(1);
}

return 0;
}

/* Einige Zeilen in die Testdatei schreiben. */


fprintf(fpOut,"Dies ist ein Test. Wert1: %d\n",i);
fprintf(fpOut,"Zweiter Test: Wert2: %f\n",f);
/*Datei schliessen */
fclose(fpOut);
/* Testdatei jetzt zum Lesen
* oeffnen
*/
if ((fpIn = fopen(datei,"r"))==NULL)
{
printf("Test-Datei %s kann nicht geoeffnet werden\n",
datei);
exit(1);
}
/* Zeilen wieder auslesen */
fscanf(fpIn,"Dies ist ein Test. Wert1: %d\n",&j);
fscanf(fpIn,"Zweiter Test: Wert2: %f\n",&g);
/*Datei schliessen */
fclose(fpIn);

Prof. Dr. U. Matecki

Seite 197

Prof. Dr. U. Matecki

Seite 198

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

2.6.2.3 Byteorientiertes Lesen und Schreiben


Bei den bisher behandelten Lese- und Schreibfunktionen wurde
zeichenorientiert gelesen und geschrieben, d.h. z. B. ein floatWert 13.33345678853 wurde z. B. als Zeichenkette mit 14 Zeichen auf eine Datei geschrieben, ein int-Wert 3994888 wurde
mit 7 Zeichen geschrieben.
Was aber, wenn ein float-Wert exakt mit der Genauigkeit, mit
der er im Rechner steht (Mantisse+Exponent!!) auf Datei geschrieben werden soll und in spteren Rechnungen auch wieder so gelesen werden soll?
Oder wir wollen eine Bilddatei mit RGB-Werten fr jedes Pixel
auf Datei schreiben. Hier ist es ebenfalls sinnvoll, nicht fr jedes
Pixel 20 Bytes oder mehr zu verbrauchen, nur weil jede Ziffer
eines Wertes 1 Byte braucht.

Die Funktion fread() liest aus fp in den Speicherbereich, auf


den ptr zeigt hchstens nobj Objekte der Gre size Bytes
ein. Falls die Datei kleiner ist, wird weniger eingelesen. Die
Funktion liefert die Anzahl der eingelesenen Objekte zurck.
Objekt 0 Objekt 1

ptr:
fp = fopen(meinedatei.o,rb);
fread(ptr, 3, 2, fp);

meinedatei.o

 Ziel: Werte so auf Datei schreiben, wie sie in unserem Programm reprsentiert werden (z. B. 4 Bytes fr einen int-Wert).
 Byteorientiertes Lesen und Schreiben einer Datei.

Dateizeiger in *fp
nach fread()

Hierzu gibt es die Funktionen

Die Funktion fwrite() schreibt auf die Datei, die durch fp


reprsentiert wird, nobj Objekte der Gre size, die im Speicherbereich stehen, auf den ptr zeigt.

size_t fread(void *ptr,


size_t size,
size_t nobj,
FILE *fp);
und
size_t fwrite(void *ptr,
size_t size,
size_t nobj,
FILE *fp);
Der Datentyp size_t ist auf den meisten Systemen ganz einfach int.
Prof. Dr. U. Matecki

Seite 199

Prof. Dr. U. Matecki

Seite 200

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


Beispiel: Das Kopierprogramm aus file1.c mit fread() und
fwrite():

Objekt 0 Objekt 1

ptr:
fp = fopen(meinedatei.o,wb);

file3/file3.c:

fwrite(ptr, 3, 2, fp);

#include <stdio.h>

void usage(char *progname)


{
printf("Usage:\n");
printf("%s <Eingabedatei> <Ausgabedatei>\n",progname);
}

meinedatei.o

Dateizeiger in *fp nach


fwrite()

Der Zugriff mit diesen beiden Funktionen mu mit den Funktionen


int feof(FILE *fp);
und

int main(int argc, char *argv[])


{
char *dateiIn, *dateiOut;
char buffer[5];
FILE *fpIn, *fpOut;
int anzahl;
/* Wurde zwei Kommandozeilenargumente mitgegeben? */
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
dateiIn = argv[1];
dateiOut = argv[2];

int ferror(FILE *fp);


berprft werden.
Die Funktion feof() liefert einen von 0 verschiedenen Wert,
wenn der Dateizeiger in *fp am Dateiende angekommen ist.
Die Funktion ferror() liefert einen von 0 verschiedenen Wert,
wenn fr fp ein Fehler notiert wurde.

Prof. Dr. U. Matecki

Seite 201

/* Eingabedatei zum Lesen oeffnen, Ausgabedatei zum Schreiben


* oeffnen
*/
if ((fpIn = fopen(dateiIn,"r"))==NULL)
{
printf("Eingabe-Datei %s nicht vorhanden\n",dateiIn);
exit(1);
}
if ((fpOut = fopen(dateiOut,"w"))==NULL)

Prof. Dr. U. Matecki

Seite 202

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

if ((fpOut = fopen(dateiOut,"w"))==NULL)
{
printf("Ausgabe-Datei %s kann nicht geoeffnet werden\n",
dateiOut);
exit(1);
}
/* Inhalt von Eingabedatei in Ausgabedatei kopieren.
* Es werden jeweils 5 Bytes eingelesen und 5 Bytes
* geschrieben. Beim letzten Zugriff koennen weniger
* Bytes gelesen/geschrieben werden.
*/
while (!feof(fpIn))
{
anzahl = fread(buffer, sizeof(char), 5, fpIn);
fwrite(buffer, sizeof(char), anzahl, fpOut);
printf("Anzahl Bytes: %d\n",anzahl);
}

/* Dateien wieder schliessen */


fclose (fpIn);
fclose (fpOut);
return 0;

2.7 Zeiger auf Funktionen (Function


Pointer)
Bisher haben wir Zeiger auf Datenbereiche (z. B. Zeichenketten, Matrizen oder Strukturen) betrachtet. Wir haben dabei gelernt: Eine Zeigervariable ist nichts anderes als eine Variable,
die eine Adresse enthlt. Kurz: Zeiger sind Adressen.

2.7.1 Grundlagen
Der Code einer C-Funktion liegt nach Programmstart natrlich
ebenfalls an irgendeiner Speicheradresse. Diese Speicheradressen sind in C ebenfalls ansprechbar. Funktionszeiger werden hufig dann vereinbart, wenn erst zur Laufzeit des Programms entschieden werden soll, welche Funktion aufgerufen
werden soll.
Ein Funktionszeiger wird etwas komplizierter vereinbart wie ein
Zeiger auf einen Datenbereich, nmlich:
ReturnTypeOfFunction (*nameOfPointerVar) (ParameterList)

Bedeutung der einzelnen Bestandteile der Vereinbarung


1

Return-Typ der Funktion, auf die der Zeiger


spter zeigen soll

Name der Zeigervariablen. Dieser wird -- wie jede


Zeigervariable -- mit * eingeleitet. Allerdings wird
der Name des Funktionszeigers in ( ) gesetzt.

Prof. Dr. U. Matecki

Seite 203

Parameterliste der Funktion, auf die der Zeiger


zeigen soll. Sie kann leer sein oder eine Liste der
Datentypen der Funktionsparameter enthalten.
Prof. Dr. U. Matecki
Seite 204

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Beispiel fr die Vereinbarung eines Funktionszeigers, der auf


eine Funktion zeigt, die nichts zurckgibt und eine leere Parameterliste besitzt:

Die Zuweisung der Startadresse der Funktion myfunc() an die


Zeigervariable myptr erfolgt ganz einfach durch Zuweisung
des Funktionsnamens an die Zeigervariable.

void (*myfuncptr)();

Von der Anwendung von Zeigern auf Datenbereiche wissen wir:


Sobald der Zeiger eine gltige Adresse enthlt, kann (und wird)
er auch dereferenziert werden. Bei Funktionszeigern bedeutet
dies: Die Funktion, deren Einsprungadresse der Zeiger enthlt,
wird durch die Dereferenzierung aufgerufen. Bei unserem kleinen Beispiel von oben sieht dies folgendermaen aus:

Beispiel fr die Vereinbarung eines Funktionszeigers, der auf


eine Funktion zeigt, die int zurckgibt . Die Funktion soll als
ersten Parameter eine int-Variable und als zweiten Parameter
eine float-Variable bekommen:
int (*myfuncptr) (int, float);

functionpointer1.c:
ACHTUNG: Nach der Vereinbarung zeigt der Funktionszeiger,
wie jeder Zeiger in C, zunchst einmal auf nichts: Er enthlt
also eine ungltige Adresse. Die Zuweisung einer gltigen
Einsprung-Adresse einer Funktion erfolgt folgendermaen:
void myfunc()
{
printf("Hallo");
}

void myfunc()
{
printf("Hallo");
}
int main()
{
void (*myptr)();
myptr = myfunc;

int main()
{
void (*myptr)();

/* Dereferenzierung von myptr heisst:


* Aufruf der Funktion, auf die myptr
* zeigt, also: Aufruf von myfunc()
*/
(*myptr)();

/*Setze myptr auf die Einsprungadresse


* der Funktion myfunc()
*/
myptr = myfunc;

return 0;
}

return 0;
}

Die Klammern (*myptr) bewirken dabei, dass die Dereferenzierung als erstes ausgefhrt wird.
Prof. Dr. U. Matecki

Seite 205

Prof. Dr. U. Matecki

Seite 206

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Es folgt ein zweites kleines Beispiel, bei dem anhand einer


Kommandozeilen-Option entschieden wird, auf welche Funktion
der Funktionszeiger gesetzt wird, bevor er aufgerufen wird.

int main(int argc, char *argv[])


{
/* Abschnitt 1: Das ist unser
* Funktionszeiger.
*/
void (*giblaut)(int);

functionpointer2/functionpointer2.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* So oft soll gebellt/miaut werden */


int anzahl;

void usage(char *prog)


{
printf("usage: %s {-katze | -hund}
anzahl_Laute\n",prog);
}

if (argc < 3)
{
usage(argv[0]); exit(1);
}

/********************************************/
/* Auf eine dieser beiden Funktionen wird
* der Funktionszeiger zeigen
*/
void gibLaut1(int anz) {
int i;
for(i=0;i<anz;i++)
{
printf("wau\n");
}
}
void gibLaut2(int anz) {
int i;
for(i=0;i<anz;i++)
{
printf("miau\n");
}
}
/********************************************/

anzahl = atoi(argv[2]);
/* Abschnitt 2: Hier wird der Funktions* zeiger auf eine der beiden Funktionen
* gesetzt.
*/
if(strcmp(argv[1],"-hund")==0)
{
/* Funktionszeiger auf erste Funktion
* setzen
*/
giblaut = gibLaut1;
}
else if(strcmp(argv[1],"-katze")==0)
{
/* Funktionszeiger auf zweite Funktion
* setzen
*/
giblaut = gibLaut2;
}

Prof. Dr. U. Matecki

Seite 207

Prof. Dr. U. Matecki

Seite 208

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

else
{
usage (argv[0]); exit(1);
}
printf("%s:\n",argv[1])

2.7.2 Simulation von OOP in C: Funktionszeiger in Strukturen

/* Abschnitt 3: Funktionszeiger derefe* renzieren: Die Funktion auf die der


* Zeiger zeigt, wird aufgerufen
*/
(*giblaut)(anzahl);
return 0;
}
Wenn das Programm wird z. B. aufgerufen wird mit
functionpointer2

-katze 5

so wird der Funktionszeiger giblaut im unteren Teil von Abschnitt 2 auf die Startadresse der Funktion gibLaut2() gesetzt. Dies geschieht einfach durch Zuweisung des Funktionsnamens an die Zeigervariable giblaut. In Abschnitt 3 wird in
diesem Falle bei der Dereferenzierung des Funktionszeigers
die Funktion gibLaut2() mit aktuellem Parameter anzahl=5
aufgerufen. Die Funktion gibLaut2() gibt daraufhin 5 mal
"miau" aus.

Sie haben in Java bereits das Konzept von Klassen mit Attributen und Methoden, sowie das Konzept der Datenkapselung
kennengelernt. Weiterhin haben Sie in C bereits das Konzept
von Strukturen (struct) kennengelernt. Bislang waren CStrukturen nichts anderes als Klassen, welche nur public Attribute, jedoch keine Methoden kennen. Auerdem war in C
der Begriff der Datenkapselung (Information Hiding) nur bedingt
bekannt, da es in C keine Klassen gibt und auch die verschiedenen Zugriffsarten public, protected und private innerhalb von Strukturen erst in C++ bekannt sind.
Zumindest das Konzept von Klassen mit public-Attributen und
public-Methoden lsst sich in C recht gut ber Strukturen und
Funktionszeiger simulieren. Hierzu betrachten wir zunchst ein
Java-Programmsystem, welches eine hnliche Funktionalitt
aufweist, wie das vorherige Beispiel functionpointer2.

Wir haben eine abstrakte Basisklasse GrossesTier, welche


eine abstrakte Methode gibLaut() enthlt. Von ihr sind die
zwei Klassen Hund und Katze abgeleitet, welche jeweils eine
Prof. Dr. U. Matecki

Seite 209

Prof. Dr. U. Matecki

Seite 210

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Implementation der Methode gibLaut() enthlt. Die Klasse


GibLaut enthlt die main()-Methode. Sie entscheidet anhand der Kommandozeilen-Parameter, ob ein Objekt vom Typ
Hund oder eines vom Typ Katze erzeugt wird, welches anschlieend Laut gibt. Wir zeigen kurz die Quelltexte des JavaSzenarios:

Hund.java:
public class Hund extends GrossesTier {
public void gibLaut(int anzahl) {
for(int i=0;i<anzahl;i++){
System.out.println("wau");
}

GrossesTier.java:
public abstract class GrossesTier {
private String name;
private int alter;

}
}

public String getName() {

Die Klasse Hund ist eine dieser abgeleiteten Klassen. Ihre


Methode gibLaut() gibt hier anzahl mal den Laut wau aus.

return name;
}
public void setName(String name) {

public class Katze extends GrossesTier {


public void gibLaut(int anzahl) {

this.name = name;
}
public int getAlter() {

for(int i=0;i<anzahl;i++){
System.out.println("miau");
}

return alter;
}
public void setAlter(int alter) {

}
}
Die Klasse Katze die zweite abgeleitete Klasse. Ihre Methode
gibLaut() gibt hier anzahl mal den Laut miau aus.

this.alter = alter;
}
public abstract void gibLaut(int anzahl);
}
Die abstrakte Basisklasse enthlt als zentrale, abstrakte
Methode die Methode gibLaut(), welche in einer oder mehreren
abgeleiteten Klassen implementiert wird.

Prof. Dr. U. Matecki

Seite 211

Prof. Dr. U. Matecki

Seite 212

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

public class GibLaut {


t.gibLaut(anzahl);

// gibLaut() aufrufen

public void usage(){


String progname = new String("GibLaut");
System.out.println("Aufruf:");
System.out.println(
progname + " " +
"{-hund | -katze} anzahl");

} // end main()
} // end class
Die Klasse mit der main()-Methode knnte z. B. mit
java GibLaut katze 5

}
public static void main(String[] args) {

aufgerufen werden. In diesem Falle wird ein Objekt t vom Typ


Katze erzeugt. Dessen gibLaut()-Methode gibt anschliessend 5 mal miau aus.

GibLaut laut = new GibLaut();


GrossesTier t=null;
int anzahl;

Wie kann ein solcher Ansatz nun grob in C nachmodelliert


werden? Zunchst definieren wir uns eine Struktur

if (args.length != 2) {
laut.usage();
System.exit(1);
}

functionpointer3.c:

// Kommandozeile auswerten:
// Anzahl Laute stehen im zweiten Param.
anzahl = Integer.parseInt( args[1]);
// Tierart steht im ersten Param.
if(args[0].startsWith("-katze")) {
t = new Katze();
}
else if(args[0].startsWith("-hund")){
t = new Hund();
}
else {
laut.usage();System.exit(-1);
}

Prof. Dr. U. Matecki

Seite 213

typedef struct gt {
char *name;
int alter;
void (*giblaut)(int);
}grossesTier;
Sie enthlt, hnlich wie die Java-Klasse GrossesTier einige
Eigenschaften unseres Tieres, z. B. Name und Alter, als
Attribute. Statt einer abstrakten Methode (kennen wir in C ja
nicht) hinterlegen wir einen Zeiger auf eine Funktion, die nichts
zurckgibt und einen int-Parameter bergeben bekommt.

Prof. Dr. U. Matecki

Seite 214

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Danach schreiben wir wie im C-Beispiel functionpointer2.c die beiden Funktionen gibLaut1() und gibLaut2():

Die main()-Funktion ist nun zustndig fr das Setzen des


Funktionszeigers innerhalb der Struktur und fr das Dereferenzieren des Funktionszeigers:

void gibLaut1(int anz) {


int i;
for(i=0;i<anz;i++)
{
printf("wau\n");
}
}

int main(int argc, char *argv[])


{
grossesTier g;
int anzahl;
if (argc < 4) {
usage(argv[0]); exit(1);
}

void gibLaut2(int anz) {


int i;
for(i=0;i<anz;i++)
{
printf("miau\n");
}
}

/*Alter und Anzahl Laute stehen in


* argv[2] und argv[3]
*/
g.alter = atoi(argv[2]);
anzahl = atoi(argv[3]);

Beide Funktionen geben anz mal den Laut, fr den sie zustndig sind, aus.

Prof. Dr. U. Matecki

Seite 215

/*Tierart steht in argv[1]


*/
if(strcmp(argv[1],"-hund")==0)
{
g.name="hund";
/* Setze den Funktionszeiger auf die
* Startadresse von gibLaut1()
*/
g.giblaut=gibLaut1;
}
else if(strcmp(argv[1],"-katze")==0)
{
g.name="katze";
/* Setze den Funktionszeiger auf die
* Startadresse von gibLaut2()
*/
g.giblaut = gibLaut2;
}

Prof. Dr. U. Matecki

Seite 216

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

else
{
usage (argv[0]);
}

3 Teil 2: Objektorientierte Programmierung in ANSI C++

printf("%s %d\n",g.name,g.alter);

3.1 Handwerkliches: Umgang mit dem


Compiler "g++"

/*Dereferenzierung des Funktionszeigers


* g.giblaut  Aufruf der Funktion, auf
* die g.giblaut zeigt. Die Funktion be* kommt als aktuellen Parameter die Vari* able anzahl bergeben. Sie sagt, wie oft
* gebellt oder miaut wird.
*/

Bisher in ANSI C verwendet: Compiler gcc


Neu in ANSI C++:
 Compiler g++
 Namenskonvention der C++-Quelldateien:

(*g.giblaut)(anzahl);

Nicht mehr Endung .c sondern


.C, .cpp, .cc, .cxx je nach Firmenstandard

return 0;
}
hnlich wie im Beispiel functionpointer2 wird auch hier
durch Dereferenzierung des Funktionszeigers die Funktion
aufgerufen, auf die er zeigt. Der einzige Unterschied ist, dass
der Zeiger diesmal ein Attribut einer Struktur ist.

 Compileraufruf fr Quelldateien mit z. B. Endung .cpp:


g++ -c dateiname.cpp
 liefert Objektdatei mit Endung .o

 Methoden in Klassen knnen in C als Zeiger auf Funktionen nachmodelliert werden. Diese Zeiger werden einfach als Attribute in eine Struktur eingehngt.

 Linkeraufruf fr das Zusammenbinden der Objektdateien


g++ -o programmname *.o
 bindet Objektdateien zum ausfhrbaren Programm.
Gleich bleibt:
 Verwendung des Preprozessors (#include, #ifndef,
#define, usw.)

Prof. Dr. U. Matecki

Seite 217

Prof. Dr. U. Matecki

Seite 218

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

3.2 Grundlagen der Programmiersprache C++


3.2.1 Vergleich mit C / Wichtigste Unterschiede und Gemeinsamkeiten
3.2.1.1 Was bleibt gleich?

 Funktionen, Operatoren und Methoden knnen in C++


berladen werden (gleicher Funktionsname / Methodenname fr unterschiedliche Parameterlisten.
 aus
sortInt(int iarray[], int len);
sortChar(char carray[], int len);
wird in C++

 C++ kann C-Code bersetzen und verstehen  C++ baut


auf C auf

sort(int iarray[], int len);


sort(char carray[], int len);

 C++ kennt, wie C in gleicher Syntax


 C++ kennt Exception-Handling, hnlich wie Java

o Funktionen
o Strukturen  Hier gibt es allerdings Erweiterungen
gegenber C
o Pointer und Arrays
o alle primitiven Datentypen aus C (int, float, char,
usw.)

3.2.1.2 Was kommt hinzu?


 C++ kennt Klassen, Vererbungsmechanismen und Objekte
 C++ kennt Referenzen und "Call-by-Reference" fr
Funktionen und Methoden
 C++ stellt einige zustzliche Operatoren zur Verfgung
 Speicherallokation und Freigabe geschieht nicht mehr
ber die Funktionen malloc() und free(), sondern
ausschliesslich ber die Operatoren new und delete.

Prof. Dr. U. Matecki

Seite 219

Prof. Dr. U. Matecki

Seite 220

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

3.2.2 Grundlagen der Sprache C++

Beispiel: Zunchst ein bekanntes Szenario aus C:

3.2.2.1 Referenzen und Call-by-Reference


Eine Referenz ist in C++ ein "Alternativname" fr ein
Datenobjekt und wird mit dem &-Operator vereinbart:
int i=1;
int &r = i;
 r und i enthalten den gleichen int-Wert und stehen an der
gleichen Adresse im Hauptspeicher.
Auf r kann z. B. mit dem ++-Operator zugegriffen werden:

int aendereNix(int wert)


{
return ++wert;
}
int main()
{
int w = 3;

3
int result;
result = aendereNix(w);

r++;

return 0;

Wirkung: r und i enthalten beide anschliessend den Wert 2, da


sie ja an der gleichen Stelle stehen.
Am hufigsten werden Referenzen zur Spezifizierung von
Funktions- oder Methodenargumenten verwendet. Auf diese
Weise kann eine Funktion oder Methode den Inhalt
bergebener Variablen ndern, so dass fr Aufrufer der
Funktion diese nderung anschliessend sichtbar ist.

Prof. Dr. U. Matecki

3
4

Seite 221

Der Funktion aendereNix() wird ein int-Wert bergeben (call


by value). Dieser wird innerhalb der Funktion gendert. Die nderung ist jedoch von auen nicht sichtbar, da aendereNix()
eine Kopie verndert (rot), whrend der Originalwert (blau) an
seiner Speicherstelle unverndert bleibt.

Prof. Dr. U. Matecki

Seite 222

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

In C++ wird dieser Mechanismus nun etwas einfacher:


Um eine solche nderung in C (noch nicht C++!!) nach aussen
in der aufrufenden Funktion (hier main()) sichtbar zu machen,
muss die Funktionsdeklaration und der Funktionsaufruf
folgendermassen gendert werden:

int aendereWasInCPlusPlus(int &wert)


{
return ++*wert;
}
int main()
{
int w = 3;

int aendereWasInC(int *wert)


{
return ++*wert;
}

int result;

int main()
{
int w = 3;
int result;

result = aendereWasInCPlusPlus(w);

return 0;

Adresse von w
wird bergeben

result = aendereWasInC(&w);

return 0;
}

Der Funktionsprototyp wurde abgendert, so dass jetzt die


Adresse des zu verarbeitenden int-Wertes bergeben wird.
Der Wert an der bergebenen Adresse wird von der Funktion
aendereWasInC() inkrementiert. Anschliessend ist, da
main() ja auf die gleiche Speicherstelle wie die Funktion
aenderWasInC() zugreift, die nderung auch in main()
sichtbar.

Prof. Dr. U. Matecki

Referenz auf w
wird bergeben

Seite 223

Die Funktion aendereWasInCPlusPlus() bekommt eine


Referenz auf den zu ndernden int-Wert bergeben.
Mit anderen Worten: Die Variable wert steht der Funktion
aendereWasInCPlusPlus() steht an der gleichen
Speicherstelle wie die lokale Variable w von main().
Sie kann jedoch innerhalb von aendereWasInCPlusPlus()
genau so behandelt werden, wie eine einfache Wertbergabe.
Es muss hier also nach der Funktionsvereinbarung nicht mehr
mit Zeigern und Adressen operiert werden.

Prof. Dr. U. Matecki

Seite 224

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


Zunchst ein einfaches Java-Beispiel, welches anschliessend
in C++ modelliert wird:

3.2.2.2 Klassen und Objekte in C++ /


Konstruktoren und Destruktoren
C++ ist wie Java eine objektorientierte Programmiersprache,
die Klassen, Methoden und Vererbung kennt.

Verzeichnis Java/Student:
public class Student{

Wichtigste Gemeinsamkeiten mit Java:


private
private
private
private

 Objektorientierte Sprache, die Klassen und Objekte mit


Attributen und Methoden kennt.
 Instanziierung und Initialisierung eines Objekts erfolgt mit
Hilfe des Konstruktors, der mit dem new-Operator aufgerufen wird.

String vorname;
String nachname;
int matrikel;
int semester;

public Student(String vor, String nach, int matr, int sem)


{
this.vorname = vor;
this.nachname = nach;
this.matrikel = matr;
this.semester = sem;
}

 C++ kennt, wie Java, Vererbungsmechanismen. Allerdings


kennt C++ auch sog. Mehrfachvererbung.

public void printStud()


{
System.out.println("Vorname: " + this.vorname);
System.out.println("nachname: " + this.nachname);
System.out.println("matrikel: " + this.matrikel);
System.out.println("Semester: " + this.semester);
}

Wichtigste Unterschiede zu Java:


 main() ist keine Methode einer Klasse, sondern eine
eigenstndige Funktion.
 Es gibt keinen Garbage-Collector!!!!
 Mit new erzeugte Objekte mssen mit dem deleteOperator wieder aufgerumt werden.

public static void main(String[] args) {

Student s = new Student("Alois","Ruebezahl",123,20);

 C++ kennt nicht nur Methoden, die ein Objekt einer Klasse
aufbauen und initialisieren (Konstruktoren), sondern auch
Methoden, die ein Objekt einer Klasse wieder "abbauen"
(Destruktoren).Wird der Operator delete fr ein Objekt
aufgerufen, so ruft dieser automatisch dessen Destruktor
auf.

s.printStud();

}
}

// end main

// end class Student

 C++ kennt Zeiger


 this ist keine Referenz, wie in Java, sondern ein Zeiger
Prof. Dr. U. Matecki

Seite 225

Prof. Dr. U. Matecki

Seite 226

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

In C++ wird wie in C bei Funktionsprototypen zwischen der


Deklaration einer Klasse mit ihren Methoden ( Headerdatei
mit Endung .h) und der Implementierung der Methoden einer
Klasse unterschieden (vgl. Interfaces in Java).

Die Implementierung der Methoden der Klasse Student1 erfolgt


in der Datei Student1.cpp.
Die Syntax der Methodenimplementierung sieht dabei immer
folgendermassen aus:

Zunchst die Deklaration der Klasse Student1 in der Headerdatei Student1.h:


Rueckgabetyp Klassenname::Methodenname (Parameter )
{
Statements;
}

Verzeichnis C++/Student1:
Student1.h

Beim Konstruktor wird kein Rckgabetyp angegeben, da ein


Konstruktor in C++ niemals einen Wert zurckgibt.

#ifndef STUDENT_H
#define STUDENT_H
// Klassenvereinbarung
class Student1 {

Verzeichnis C++/Student1:

// private Attribute der Klasse (hier nur matnr).


private:
int matnr;

Student1.cpp

#include <stdio.h>
#include "Student1.h"

// public - Methoden der Klasse


public:
// Konstruktor. ACHTUNG: Konstruktoren haben nie einen Rueckgabewert.
Student1(int mat);
// Methode, die die Attribute auf den Bildschirm ausgibt.
void printStud();

// Konstruktor.
Student1 :: Student1(int mat)
{
// matnr. ist als Attribut der Klasse Student allen ihren Methoden bekannt,
// da die Klassenvereinbarung mit #include eingebunden wurde.
matnr = mat;
}

};
// Funktion, die die Klassenattribute auf den Bildschirm ausgibt.
void Student1 :: printStud()
{
printf("Matnr: %d\n", matnr);
}

#endif

Prof. Dr. U. Matecki

Seite 227

Prof. Dr. U. Matecki

Seite 228

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die main()-Funktion wird in einer separaten Quelldatei gespeichert und sieht in unserem Fall folgendermassen aus:

Erweiterung der Klasse Student1, so dass auch der Name des


Studenten verarbeitet wird. Zunchst wieder die Header-Datei:
Verzeichnis C++/Student2:

Verzeichnis C++/Student1:

Student2.h
StudentTest.cpp
#include "Student1.h"

#ifndef STUDENT2_H
#define STUDENT2_H

int main()
{
// Lege lokal ein Objekt vom Typ Student1 an.
// Der Konstruktor bekommt den Wert 123 als Uebergabeparameter.
Student1 s(123);

// Klassenvereinbarung
class Student2 {
// private Attribute der Klasse (hier : matnr, vorname, nachname).
private:
int matnr;
char *vorname;
char *nachname;

// Rufe die Methode printStud() von s auf.


s.printStud();
}

Wir sehen hier, dass in C++ fr einen Konstruktoraufruf nicht


zwangslufig ein new-Aufruf notwendig ist, wie es in Java der
Fall wre.
Der Operator new wrde das Objekt s (hnlich wie malloc() )
auf dem Freispeicher anlegen, so dass es auch nach Verlassen
der aufrufenden Funktion noch zur Verfgung steht. Da hier
jedoch main() die aufrufende Funktion ist, wurde das Objekt s
einfach auf dem Stack angelegt.
Die Alternative mit new knnte folgendermassen aussehen:
Student1 *s;
s = new Student1(123);
s->printStud();
delete s;
In beiden Fllen wird beim Einrichten eines Objektes sein
Konstruktor aufgerufen.

public:
// Konstruktor. ACHTUNG: Konstruktoren haben nie einen Rueckgabewert.
Student2(int mat, char *vorn, char *nachn);
// Destruktor. ACHTUNG: Destruktoren haben nie einen Rueckgabewert.
~Student2();
// Methode, die die Attribute auf den Bildschirm ausgibt.
void printStud();
};

Bei den Attributen sind zwei char-Zeiger hinzugekommen:


vorname und nachname
Bei den Methoden wird der Konstruktor um den zu bergebenden Vornamen und Nachnamen erweitert.
Weiterhin wurde, da im Konstruktor Speicher fr die Attribute
vorname und nachname im Freispeicher reserviert werden
muss, ein Destruktor eingefhrt, der diesen beim Abbauen des
Objektes wieder freigibt. Die Syntax eines Destruktors in der
Klassendeklaration sieht immer folgendermassen aus:
~ Klassenname();

Prof. Dr. U. Matecki

Seite 229

Prof. Dr. U. Matecki

Seite 230

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die Implementierung der Methoden der Klasse Student2 erfolgt, wie gewohnt, in einer separaten Quelldatei namens
Student2.cpp:

Der Konstruktor erledigt hier folgende Aufgaben:

Verzeichnis C++/Student2:
Student2.cpp

 Allokation von Speicherplatz fr die Attribute vorname und


nachname. Der Speicherplatzbedarf wird vorher mit der CFunktion strlen() anhand der bergabeparameter ermittelt. Die Allokation eines Arrays geschieht hierbei in der
Syntax

#include <stdio.h>
#include <string.h>
#include "Student2.h"

variablenname = new datentyp[groesse];

// Konstruktor
Student2 :: Student2(int mat, char *vor, char *nach)
{
int len;

Anders als beim Aufruf der Funktion malloc() in C gibt


groesse hierbei die Anzahl der einzurichtenden Objekte
an, nicht aber die Speicherplatzgrsse in Bytes.

//Speicherplatz fuer vorname und nachname mit


// new allokieren. (In C++ kein malloc() mehr
// nutzen!!!)
len = strlen(vor)+1;
vorname = new char[len];

 Initialisierung der Attribute des Objektes mit konkreten


Werten. Das int-Attribut matnr wird einfach per Zuweisung mit dem Wert des bergebenen Parameters mat initialisiert. Die beiden char-Zeiger werden per strcpy()
mit dem Inhalt der bergebenen Parameter vor und nach
initialisiert.

len = strlen(nach)+1;
nachname = new char[len];
strcpy(vorname, vor);
strcpy(nachname, nach);
matnr = mat;
}

Prof. Dr. U. Matecki

Seite 231

Prof. Dr. U. Matecki

Seite 232

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++


ACHTUNG: Niemals mit dem C++-Operator new allokierten
Speicher mit der C-Funktion free() wieder freigeben. Niemals
mit der C-Funktion malloc() bzw. calloc() allokierten
Speicher mit dem C++-Operator delete wieder freigeben!!!!
Dies fhrt zu schwer auffindbaren Laufzeitfehlern und Programmabstrzen!!

// Destruktor
Student2 :: ~Student2()
{
delete [] vorname;
delete [] nachname;
}

Der Destruktor eines Objektes wird immer automatisch aufgerufen, wenn der Operator delete dieses Objekt lscht. Er ist
dafr zustndig, den vorher im Konstruktor oder in anderen Methoden dynamisch reservierten Speicherplatz der Objekt-Attribute wieder freizugeben.

 In C++ fr die Speicherplatzreservierung und freigabe nur


noch mit new und delete arbeiten!

In der Klasse Student2 wurde fr die Attribute vorname und


nachname im Konstruktor dynamisch Speicher mit dem newOperator allokiert. Dieser im Destruktor der Klasse Student2
mit dem delete-Operator wieder freigegeben.
Es gibt in C++ 2 Ausprgungen des delete-Operators:
delete objektzeiger;
ruft den Destruktor eines einzelnen Objektes und gibt den
Speicher, auf den objektzeiger zeigt, frei, wie in Beispiel
Student1.
Der Aufruf
delete [] objektzeiger;
hingegen ruft die Destruktoren aller Objekte eines Arrays auf
und gibt den Speicher, auf den objektzeiger zeigt, wieder
frei.

Prof. Dr. U. Matecki

Seite 233

Prof. Dr. U. Matecki

Seite 234

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

// Methode, die die Klassenattribute auf den Bildschirm ausgibt.


void Student2 :: printStud()
{
printf("Matnr: %s\n", vorname);
printf("Matnr: %s\n", nachname);
printf("Matnr: %d\n", matnr);
}

3.2.2.3 Ein- und Ausgabe in C++: Erste Schritte


In C verwendet: Die Funktionen printf() und scanf() bzw. fprintf()
und fscanf();
Neu in C++: Stream-Operatoren

Die Methode printStud() hat sich (noch) nicht wesentlich verndert. Sie verwendet derzeit zur Ausgabe der Attribute die
bekannte C-Funktion printf().

<<

fr die Ausgabe auf einen sog. Ausgabestrom ("sende


nach")

>>

fr die Eingabe von einem sog. Eingabedatenstrom


("empfange von")

Verzeichnis C++/Student2:
StudentTest.cpp
int main()
{

Beide Operatoren sind fr alle Standard-Datentypen in der


Headerdatei <iostream> vereinbart.

Student2 *s = new Student2(123, "Hans", "Meier");


// Falls die Speicherallokation nicht geklappt hat, gibt new
// den echten Zahlenwert 0 zurueck.
if (s != 0)
{
// Da s ein Pointer ist, wird auf seine Attribute und Methoden
// wie bei C-Strukturen mit -> zugegriffen.
s->printStud();

Ausgabe auf die Standard-Ausgabe bzw. Standard-Fehlerausgabe (Bildschirm):


erfolgt in C++ ber den Ausgabe-Datenstrom cout bzw. cerr:
Ausgabe auf die Standard-Ausgabe

delete s;

Frher in C:
}
return 0;

printf(%s %d %c,Dies ist ein Test,10,\n);


}

fprintf( stdout, %s %d %c,Dies ist ein Test,10,\n);

Hier wird s mit dem new-Operator auf dem Freispeicher


eingerichtet. Dieser gibt falls kein Exception-Handling
vewendet wird 0 zurck, falls keine Speicherallokation
mglich war.
Bei Anwendung von Exception-Handling sieht das Abfangen
dieser Situation anders aus. Mehr dazu spter.

Prof. Dr. U. Matecki

Seite 235

Neu in C++:
cout << Dies ist ein Test << 10 << endl;

Stream-Operator
fr char-Array

Prof. Dr. U. Matecki

Stream-Operator
fr int

Stream-Operator
fr char

Seite 236

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

3.2.3 Einbinden der Standard Template


Library (STL)

Ausgabe auf die Standard-Fehler-Ausgabe


Frher in C:
fprintf( stderr, %s %d %c,Dies ist ein Test,10,\n);
Neu in C++:
cerr << Dies ist ein Test << 10 << endl;

Stream-Operator
fr char-Array

Stream-Operator
fr int

Stream-Operator
fr char

Die C++-STL beinhaltet einige sehr ntzliche Klassen, wie z. B.


Stream-Klassen (ostream, istream, usw) oder die Klasse
string.
Auch die Streams der Standard-Eingabe und -Ausgabe (cin
und cout) sind Objekte der Klassen istream bzw. ostream.
Diese Klassen sind in einem eigenen Namensraum (namespace) namens std untergebracht. Ein namespace ist eine
weitere Abstraktionsmglichkeit, um beispielsweise zusammengehrende Klassen einer Bibliothek zusammenzufassen.

Eingabe von der Standardeingabe (Tastatur):


erfolgt in C++ ber den Eingabedatenstrom cin:

Ein namespace hat immer einen Namen. Um nun beispielsweise Objekte der Klasse string vereinbaren zu knnen, gibt
es zwei Mglichkeiten, den namespace std zu nutzen:

Eingabe von der Standard-Eingabe:


Frher in C:
int i;
float f;

(i.)

Explizite Angabe des namespaces vor dem Datentyp:

scanf(%d %f\n, &i, &f);

std::string myString = "ene mene mu";

fscanf(stdin,%d %f\n, &i, &f);

Neu in C++:
cin >> i >> f;

Stream-Operator
fr int

Lies einen int-Wert nach i und einen


float-Wert nach f ein
Stream-Operator
fr float

(ii.)

Einmalige Angabe, da der betreffende Namensraum in


diesem .cpp-Modul genutzt wird; Danach Variablenvereinbarung wie gewohnt:
using namespace std;
string myString = "ene mene mu";

Weiterhin mu die Headerdatei jeder STL-Klasse, die wir


nutzen wollen, eingebunden werden. Die Namenskonvention
fr die STL-Header hat sich hier leicht modifiziert: Sie heit
immer (genau!!) so wie die betreffende Klasse und endet nicht
mit .h.
Prof. Dr. U. Matecki

Seite 237

Prof. Dr. U. Matecki

Seite 238

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Damit vervollstndigen wir die Vereinbarung aus (ii) zu:

#include <string>

3.2.4 berladen von Operatoren -- eine


Einfhrung

using namespace std;

3.2.4.1 Begriffe

int main()
{
string myString = "ene mene mu";
// tuwas mit myString ...

In C++ werden Operatoren wie normale Funktionen oder


Methoden behandelt. Eine Konsequenz hiervon ist, da
Operatoren berschrieben/berladen werden knnen.

return 0;

Sehr bekannt sind hier zwei Programmiertechniken:

(i.)

berladen von Operatoren als eigenstndige Funktion.


Dies wird dann bevorzugt, wenn man einen Operator neu
schreiben will, der eigentlich zu einer Klasse gehrt, die
nicht verndert werden soll. Hierbei knnte es sich z. B.
um eine STL-Klasse handeln.
Ein bekanntes Beispiel wre hier das berladen des Ausgabe-Stream-Operators, so da er in Zukunft ein Objekt
der Klasse Student4 ausgibt. Der <<-Operator mte eigentlich fr die Klasse ostream berladen werden. Deren
Standard-Verhalten wollen / drfen wir jedoch nicht ndern. Daher wird der <<-Operator fr unsere Student4Klasse als klassenunabhngige Funktion geschrieben.

(ii.)

Prof. Dr. U. Matecki

Seite 239

berladen von Operatoren als Objekt-Methoden innerhalb


einer Klasse. So knnte z. B. ein Zuweisungs-Operator fr
die Klasse Student4 eine tiefe Kopie des bergebenen Objektes bewerkstelligen. Ein solcher Operator gehrt dann
sicherlich zu der Klasse, deren Objekte er kopieren soll.

Prof. Dr. U. Matecki

Seite 240

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

3.2.4.2 Beispiel zum berladen von Operatoren


Wir verwenden wieder unsere Studierenden-Klasse. Sie enthlt
nun einige Modifikationen. Zunchst die Headerdatei der neuen
Klasse:
Student4.h:

};
// Ausgabe-Stream-Operator als eigenstaendige
// Funktion
ostream & operator << (ostream &out,
Student4 &s);

#ifndef STUDENT3_H
#define STUDENT3_H
using namespace std;
#include <iostream>
#include <string>

// einige getter()-Methoden
int getMatnr();
string getVor();
string getNach();

#endif
Header und
Namespace

// Klassenvereinbarung
class Student4 {

Die Vereinbarung der Operatoren erfolgt immer nach dem


Muster:
Rueckgabetyp operator operatorname (parameter);

// private Attribute der Klasse


private:
int matnr;
string vor;
vor und nach
string nach;
sind nun vom
Typ string!
public:

Bei dem oben gezeigten Stream-Operator wre hier also die


Zuordnung:
Rueckgabetyp operator operatorname (parameter);
ostream & operator << (ostream &out,
Student4 &s);

// Konstruktoren
Student4(int mat, string vorname,
string nachname);
Student4(Student &s);
Student4();
~Student4();
// Zuweisungsoperator
Student4& operator=(Student4 &s);

Prof. Dr. U. Matecki

Seite 241

Prof. Dr. U. Matecki

Seite 242

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Die Implementierung der neuen Studierenden-Klasse sieht nun


folgendermaen aus:
Student4.cpp:
#include "Student4.h"
using namespace std;
#include <string>
Nach der Einbindung von Namespace und Headern folgen
zunchst die Konstruktoren und der Destruktor. Sie enthalten
nichts wirklich Neues.
// Konstruktoren. Sie fertigen tiefe Kopien der
// bergebenen Parameter an
Student4 :: Student4(int mat, string vor,
string nach) {
this->vor = vor;
this->nach = nach;
matnr = mat;
}
Student4 :: Student4(Student4 &s) {
this->vor = s.vor;
this->nach = s.nach;
this->matnr = s.matnr;
}

// Zuweisungsoperator als Objekt-Methode:


// this bekommt alle Attribute der bergebenen
// Referenz zugewiesen. Anschlieend wird
//Referenz auf this-Objekt zurckgegeben
Student4& Student4 :: operator=(Student4 &s) {
vor = s.vor;
nach = s.nach;
matnr = s.matnr;
return *this;
}
Ein Zuweisungs-Operator bekommt also immer die Quelle der
Kopie als Parameter bergeben und gibt die gefllte Kopie
wieder zurck.
Es folgen einige getter-Methoden fr die privaten Attribute:

Student4 :: Student4() {
this->vor = "";
this->nach = "";
this->matnr = "";
}
// Destruktor. Tut im Augenblick fast nichts,
// da wir keine internen Zeiger haben
Student4 :: ~Student4() {
printf("Hier wird freigegeben!!\n");
}
Prof. Dr. U. Matecki

Nach den Konstruktoren folgt nun der Zuweisungs-Operator. Er


ist Objektmethode der Klasse Student4. Er bernimmt alle
Attribute des bergebenen Student4-Objektes s in die Attribute
von this. Danach wird die Referenz auf das nun gefllte this Objekt zurckgegeben.

Seite 243

int Student4 :: getMatnr() {


return matnr;
}
string Student4 :: getVor(){
return vor;
}
string Student4 :: getNach(){
return nach;
}

Prof. Dr. U. Matecki

Seite 244

Handout Programmentwicklung II -- C und C++

Handout Programmentwicklung II -- C und C++

Damit ist die Implementierung der Methoden von Student4 beendet. Es folgt als zweiter berladener Operator der AusgabeStream-Operator:
// ACHTUNG: Stream-Operator ist KEINE Methode
// der Klasse Student4.
// Wenn berhaupt, msste er Methode von
// ostream sein. Da wir aber ostream nicht
// verndern drfen, wird der <<-Operator eine
// eigenstndige FUNKTION
ostream & operator << (ostream &out,
Student4 &s) {

Die folgende Quelldatei zeigt eine einfache Testanwendung der


Klasse Student4:
StudentTest.cpp:
int main()
{
// Hier wird s dynamisch mit new
// auf dem Freispeicher angelegt.
// Fehlallokation wird diesmal mit
// bad_alloc-Exception abgefangen
Student4 *s;

return out << "Matnr: "<< s.getMatnr() << endl \


<< "Vorname: " << s.getVor() << endl \
<< "Nachname: " << s. getNach() << endl;

try{
s = new Student4(123, "Hans", "Meier");
}
catch(bad_alloc&) {
cerr << "alloc fehlschlag!" << endl;
exit(1);

}
Er gibt immer eine Referenz auf das bergebene und nun
"gefllte" ostream-Objekt zurck. Zweiter bergabeparameter
ist eine Referenz auf das auszugebende Student4-Objekt.
Dessen Attribute werden mit den seinen getter()-Methoden
abgefragt und auf das bergebene ostream-Objekt ausgegeben.

}
// Nutzung des neuen Zuweisungs// Operators!
Student4 t = *s;
// Nutzung des neuen Stream// Operators!
cout << t << endl;

return 0;
}

Prof. Dr. U. Matecki

Seite 245

Prof. Dr. U. Matecki

Seite 246

Handout Programmentwicklung II -- C und C++

4 Literatur/Quellen
GOLL2007

GOLL2005

HEROLD2004

KERNIGHAN1990

J. Goll et. al.,Java als erste


Programmiersprache,Teubner Verlag, 5.
berarbeitete Auflage, Mrz 2007
J. Goll et. al., C als erste Programmiersprache, Teubner Verlag, 5. berarb.
Auflage, August 2005
H. Herold: C-Programmierung unter
Linux / Unix / Windows, Millin Verlag, 1.
Auflage, April 2004
D. Kernighan, W. Ritchie,
Programmieren in C. ANSI C, Hanser
Verlag, 2. Auflage, Januar 1990

STROUSTRUP2000 Bjarne Stroustrup, Die C++-Programmiersprache, Addison-Wesley, 4.


Auflage, 2000
WOLF2006

Prof. Dr. U. Matecki

Jrgen Wolf, C++ von A bis Z, Galileo


Computing, Januar 1990

Seite 247

Das könnte Ihnen auch gefallen