Inhalt 4.5 Arbeit mit Zeigern (engl. Pointer)

Werbung
Inhalt
Inhalt:
4. Programmiersprache C
4.1 Programmaufbau in C
4.2 Basisdatentypen und einfache Anweisungen
4.3 Steuerfluss-Konstrukte
4.4 Arbeit mit indizierten Größen (Felder)
4.5 Arbeit mit Zeigern (Pointer)
4.6 Zeichen und Zeichenketten
4.7 Funktionen
4.8 Strukturen
4.9 Typen, Variable und Konstante
Peter Sobe
72
4.5 Arbeit mit Zeigern (engl. Pointer)
Zeiger
Ein Zeiger (engl. Pointer) speichert eine Adresse, unter der ein Wert
im Speicher des Computers gespeichert werden kann.
Eine Variable im Gegensatz speichert einen Wert.
Der Name eines Zeigers ist mit einer Adresse verbunden, ein
Variablenname dagegen mit einem bestimmten Wert.
Zeiger werden z.B. für folgende Zwecke benutzt:
 Dynamische Speicherverwaltung
 Parameterübergabe in Funktionen
 Zeichenkettenverarbeitung
Peter Sobe
73
Definition von Zeigervariablen
 Bei der Definition eines Zeigers wird ein Stern ("*") vor den
Bezeichner geschrieben.
 Werden die Bezeichner mit Kommas getrennt in einer
Definitionsliste angegeben, so muss der Stern vor jeden
einzelnen Bezeichner geschrieben werden, der als Zeiger
definiert werden soll.
long *LZ, LZ2; // LZ ist ein Zeiger, LZ2 eine Variable
float summe, * psumme; // summe ist eine Variable,
// psumme ein Zeiger
 Für eine gute Übersichtlichkeit, besser Variablen und Zeiger in
jeweils einer eigenen Zeile definieren!
float summe;
float* psumme;
Peter Sobe
74
Veranschaulichung einer Zeigervariablen
Der Speicher eines Rechners wird über Adressen verwaltet, die die
Speicherstellen (Bytes) linear durchnummerieren. Die Nummer des
angesprochenen Bytes ist die Adresse.
Hier ein Beispiel (stark vereinfacht) :
float summe;
…
float* psumme = &summe; // Zeiger mit Adresse von summe
4
0 1
4 summe
12 psumme
…
Inhalt
Adresse
Zugriff auf summe wird durch Compiler mit
Zugriff auf Adresse 4 ersetzt
Peter Sobe
75
Der &-Operator
Zeiger - Adressoperator
• Um die Adresse einer Variablen (einen sogenannten L-Wert) zu
erhalten, muss ein spezieller Operator verwendet werden.
Lvalue = location for a value
Dieser Operator wird als Adressoperator bezeichnet. Er wird mit dem
Zeichen "&" (Ampersand) symbolisiert.
Zum Beispiel:
int Io = 1024;
int* IZ = &Io; // IZ erhält die Adresse von Io
// und nicht den Wert 1024
Peter Sobe
76
Zeiger
Ein Zeiger kann auch mit einem anderen Zeiger desselben Typs
initialisiert werden.
// jetzt enthält auch IZ2 die Adresse von Io
int* IZ2 = IZ;
int ** IZ4 = &IZ; // Zeiger auf Zeiger
Peter Sobe
77
Ein Beispiel zur Benutzung von Zeigern
void swap(float* x1, float* x2) { // zwei Werte vertauschen
float temp = *x1; *x1 = *x2; *x2 = temp;
}
void swap2(float** x1, float** x2) { // zwei Zeiger vertauschen
float* temp = *x1; *x1 = *x2; *x2 = temp;
}
int main(void)
{
float a, b, *pa, *pb;
a = 1.0, b = 2.0, pa = &a, pb = &b;
swap(&a, &b); /* Tauscht die Werte a und b wirklich* /
…
swap2(&pa, &pb); /* tauscht nur die Pointer */
…
}
Peter Sobe
78
Der * - Operator
„*“ ist der Inhaltsoperator. Damit kann ein Zeiger dereferenziert werden,
d.h. auf den Wert der Variable (auf die gezeigt wird) zugegriffen werden.
Die Vereinbarung „int* pi;“ oder auch „int *pi;“ kann gelesen werden als
„der Inhalt von pi ist vom Typ int, also ist pi vom Typ Zeiger auf int“:
int i = 1234;
int* pi; // pi ist also vom Typ „Zeiger auf int“
pi = &i; // pi zeigt jetzt auf i
*pi = 5678; // i hat jetzt den Wert 5678,
// der Zugriff auf i erfolgt hier durch Dereferenzieren
// des Zeigers pi.
Dereferenzierung:
Typ* ptr; // hier wird eine Zeigervariable definiert
*ptr = ...; ... = *ptr; // hier wird ptr dereferenziert
// und damit auf den Inhalt zugegriffen
Peter Sobe
79
Zeiger und Typen
Jeder Zeiger ist mit einer Variablen eines bestimmten Typs verbunden
Der Datentyp gibt den Typ des Datenobjekts an, das mit Hilfe dieses
Zeigers adressiert wird.
Ein Zeiger des Typs int zeigt zum Beispiel auf ein Objekt des Typs int.
int intvar;
int* intZeiger = &intvar;
Um auf ein Objekt des Typs double zu zeigen, muss der Zeiger mit dem
Datentyp double definiert werden.
double dvar;
double* dZeiger = &dvar;
Peter Sobe
80
Zeiger - Speicherbedarf von Zeigern
Der Speicherplatz, der für einen Zeiger reserviert wird, muss
eine Speicheradresse aufnehmen können.
Das bedeutet, dass ein Zeiger des Typs int und ein Zeiger
auf einen Datentyp double normalerweise gleich groß sind.
Der Typ, der einem Zeiger zugeordnet ist, gibt den Inhalt und
damit auch die Größe des adressierten Speicherbereichs an.
int* pint ; // pint belegt 4 Byte
double* pdouble; // pdouble belegt auch 4 Byte
Der Speicherplatz der für Zeiger verwendte wird ist
implementierungsabhängig und typischerweise 32 oder 64
Bit (d.h. 4 oder 8 Byte lang).
Peter Sobe
81
Zugriff auf Felder durch Zeiger (1)
In C sind Variablen für Felder und Zeiger zuweisungskompatibel.
Beispiel:
int zahlenfeld[20];
int einzelzahl;
int *iptr;
iptr = zahlenfeld; // dem Zeiger kann der Name eines Feldes
// zugewiesen werden
einzelzahl = iptr[5]; // Ein Zeiger kann wie ein Feld benutzt werden
Ein Feld mit Elementen eines bestimmten Typs wird als Variable wie ein
Zeiger auf ein Element des Typs behandelt.
Gleichzeitig kann ein Zeiger auch wie ein Feldbezeichner mit
Indexklammern versehen werden.
Peter Sobe
82
Zugriff auf Felder durch Zeiger (2)
Hintergrund:
int feld[10];
int* ptr = feld; // entspricht: int* ptr = &feld[0]
Der Feldname entspricht damit der Adresse des ersten
Feldelementes, d.h. dem mit dem Index 0.
Der Zusammenhang zwischen Feldern und Zeigern wird oft bei der
Übergabe von Feldern in Funktionsparametern ausgenutzt:
void auswahlfunktion( int *feld, int index, int *wert )
{
*wert = feld [index];
}
Details zu Funktionen und deren Parametern folgen später!
Peter Sobe
83
Addition und Subtraktion bei Zeigern
Bei der Addition (oder Subtraktion) eines ganzzahligen Wertes (z.B. 2),
wird der Wert der entsprechenden Adresse um die Größe von z.B.
zwei Objekten dieses Datentyps (hier int) erhöht (oder erniedrigt).
Wenn z.B. ein char 1 Byte, ein int 4 Byte und ein double 8 Byte belegt,
dann erhöht sich bei einer Addition um 2 zu einem Zeiger, der Wert
der im Zeiger gespeicherten Adresse um 2, 8 bzw. 16 Byte.
Beispiele:
int i; // z.B. Adresse 0x0100
int j; // z.B. Adresse 0x0104
int k; // z.B. Adresse 0x0108
int feld[10]; // z.B. Adresse 0x010C, 0x0110, 0x0114 ...
int* ptr = & i; // ptr enthält Adresse von i, d.h 0x0100
ptr = ptr + 2; // ptr enthält nun 0x0108, d.h. Adresse von k
ptr++; // ptr enthält nun 0x010C, d.h. Adresse feld[0]
Peter Sobe
84
Operationen mit Zeigern (1)
Einem Zeiger kann man einen Zeiger des gleichen Typs zuweisen.
int* ptr1 = &i;
int* ptr2 = ptr1;
Ein Zeiger kann auch inkrementiert oder dekrementiert werden.
So bedeutet z.B. „+5“ hier eine Inkrementierung um 5 Schritte mit der
Schrittweite „sizeof(int)“:
int* ptr3 = ptr2 + 5;
Eine Zuweisung eines anderen Typs ist nur über Typecast möglich:
char* pchar = (char*) ptr2; // typecast
Peter Sobe
85
Operationen mit Zeigern (2)
Es kann die Differenz zweier Zeiger gebildet werden:
int feld[10];
int* ptr1 = &feld[1];
int* ptr8 = &feld[8];
int anzahl = ptr8 – ptr1; // ergibt den Wert 7
// Besser:
ptrdiff_t anzahl = ptr8 – ptr1;
( ptrdiff_t ist in cstddef bzw. stddef.h vereinbart )
Einem Zeiger darf der Wert 0 (NULL) zugewiesen werden. Dadurch wird
der Zeiger als nicht initialisierter Zeiger gekennzeichnet:
int* ptr = 0; // oder: int* ptr = NULL;
Die Vergleichsoperationen == und != sind zulässig, um zwei Zeiger auf
Gleichheit zu testen, d.h. ob sie auf die gleiche Adresse verweisen.
Dieses hat nichts mit den Werten zu tun, die die Adressen enthalten.
Peter Sobe
86
NULL - Zeiger
Es gibt einen besonderen Zeigerwert, der als Konstante NULL definiert
ist. Die interne Darstellung der NULL ist implementierungsabhängig,
aber kompatibel zur ganzzahligen Null (0). NULL kann jeder Zeigervariablen zugewiesen werden, und jede Zeigervariable kann auf NULL
getestet werden;
p = NULL; /* Beide Anweisungen sind */
p = 0; /* korrekt und bedeutungsgleich! */
...
if (p==NULL) . . . /* Alle drei */
if (!p) . . .
/* Abfragen sind korrekt */
if (p==0) . . .
/* und bedeutungsgleich! */
Diese Operationen können auch dazu verwendet werden, um zu
ermitteln, ob ein Zeiger bereits initialisiert wurde:
int *ptr = 0, *ptr2;
...
if (ptr == 0) { … // initialisiere jetzt ptr }
Peter Sobe
87
Weitere Bedeutung von Zeigern
Zeiger zur Übergabe von Parametern an Funktionen
“Call by Reference” – Parameter sind Zeiger
void function(int x, int *y)
{
*y = 2*x*x – 3*x +5;
// der Wert auf den y zeigt wird verändert
}
Zeiger für dynamische Speicherstrukturen
int *p;
int anzahl_werte; // konkreter Wert ergibt sich zur Laufzeit
…
p = malloc(anzahl_werte*sizeof(int));
…
Peter Sobe
88
Inhalt
Inhalt:
4. Programmiersprache C
4.1 Programmaufbau in C
4.2 Basisdatentypen und einfache Anweisungen
4.3 Steuerfluss-Konstrukte
4.4 Arbeit mit indizierten Größen (Felder)
4.5 Arbeit mit Pointern
4.6 Zeichen und Zeichenketten
4.7 Funktionen
4.8 Strukturen
4.9 Typen, Variable und Konstante
Peter Sobe
89
Zeichen und Zeichenketten (engl. Strings)
• Motivation - Textoperationen werden immer wichtiger
 Das menschliche Leben besteht zum großen Teil aus Sprache und
Schrift und nur wenigen Zahlen.
 Informationsverarbeitung geht zunehmend auf Klartextdaten über.
 Internet/HTML/XML ist eine reine Textgenerierung
• Typische Aufgaben bei der Zeichenkettenverarbeitung




Numerische Daten in Texte einfügen
Textdaten analysieren und Teile extrahieren
Datenbankinformationen als HTML-Text kodieren
Befehlsanweisungen an Server zusammenbauen (SQL)
Peter Sobe
90
Erweiterung des C-Typkonzepts um Zeichen
Kodierung von Zeichen
Die Darstellung von Zeichen ist standardisiert, z.B. durch
American Standards Council (ASC)
Allgemein verwendet: ASCII 1-Byte Darstellung in C
(American Standard Code for Information Interchange)
Zukünftig: Unicode 2-Byte Darstellung zur Erfassung von
Sonder-zeichen, virtuelle Tasten (z.B. Funktionstasten),
Tastenkombinationen oder allgemein fremdsprachige Zeichen
(diakritische Zeichen, kyrilisch, arabisch, asiatische Sprachen)
•Veraltet, aber noch in Benutzung:
•EBCDIC 1-Byte Darstellung (Großrechner)
Peter Sobe
91
Zeichenkodierung am Beispiel ASCII
ASCII: 1Byte kodiert ein Zeichen (bei Unicode 1 Zeichen= 2bytes)
genau binär, z.B. 0100 0000
Wertigkeit:
2726 ...
2120
d.h. 26=64 = @
Jedes Zeichen (auch nichtdruckbare) hat eine eindeutige Binärdarstellung
in ASCII. Infolge der Binärdarstellung ordnet man deshalb einem Zeichen
auch einen Zahenlwert zu.
Für nichtdruckbare Zeichen unterscheiden sich die Zahlwerte in
den 16Bit- und 32Bit-Versionen!
Grobe Einteilung (Details siehe Codetabelle ASCII):
0-31
nichtdruckbare Zeichen (8=TAB 10+13 neue Zeile)
48-57 Ziffern (48 = 30H = entspricht Ziffer 0) (39H=Ziffer 9)
65-90 Großbuchstaben A = 65
97-122 Kleinbuchstaben a = 97
Peter Sobe
92
Definition von Zeichen in C (1)
In C gibt es 2 unterschiedliche Zahlzuordnungen je nach
Typdeklaration. Die Unterschiede wirken sich aber nur für spezielle
Sonderzeichen, wie z.B. ö,ü,ß usw. aus.
a) Deklaration mit char (signed char)
Hier wird die Binärdarstellung als vorzeichenbehaftet gedeutet. Das
werthöchste Bit (links) wird als Vorzeichen gedeutet (1 ist negativ).
Der Darstellungsbereich ist damit –128 bis +127
Bsp. ö
1111 0110
-negative Zahl
wird als 2er-Komplement gedeutet:
1111 1111
-1111 0110
0000 1001
+1
0000 1010
Peter Sobe
= -10 als Zahl oder ö als Buchstabe
93
Definition von Zeichen in C (2)
b) Deklaration mit unsigned char
Hier wird die Binärdarstellung als vorzeichenlos, d.h. als
positiv gedeutet. Der Darstellungsbereich ist damit 0 bis
255
Bsp. ö
1111 0110
27
20
Wert ist deshalb 27+26+25+24+22+21=246
Bei Benutzung von Vergleichsoperatoren in C
in Zusammenhang mit Zeichen ist die Deklaration mit char
oder unsigned char für das Ergebnis entscheidend.
Peter Sobe
Informatik I, Wintersemester 11/12
94
Angabe von Zeichenkonstanten
Zeichenkonstanten werden in einfache Apostrophzeichen
eingeschlossen.
Beispiele: ‘a‘ ‘1‘ ‘%‘ ‘/‘ ‘#‘
Bestimmte Zeichen können als sogenannte
Escape-Sequenzen dargestellt werden, z.B.
‘\n‘ Zeilenvorschub
‘\\‘ Backslash
‘\‘‘ Apostroph
Nichtdruckbare Zeichen gibt man als Hexadezimalzahl ihrer
Binärdarstellung ein:
Bsp. 1111 0110
f
6
‘\xf6‘
Peter Sobe
95
Deklarationsmöglichkeiten von Zeichen in C
Deklarationen können in allen Gültigkeitsbereichen, d.h.
auch auf globalem Niveau unter Angabe des Typs char
oder unsigned char erfolgen.
char a,z; bzw. unsigned char f,u2,z0;
Eine Initialisierung mit einem Wert ist bei der Deklaration
möglich:
char a,z= ‘%‘; bzw.
unsigned char f= ‘\\‘,k1,z0;
Namen für Zeichenkonstanten können durch eine
Präprozessor define-Anweisung festgelegt werden:
#define kreuz ‘#‘
Peter Sobe
96
Ausgabe von Zeichen mit printf() oder putchar
Bei Benutzung der Ein- u. Ausgabe muss immer
#include <stdio.h> eingebunden werden
Standardausgabe mit printf-Funktion:
char c,glob;
printf(“\nc=%5c glob=%2c“,c,glob);
% leitet Formatangabe ein
5 ist die Feldbreite für die auszugebenden Zeichen
c ist das char-Format
Zeichenausgabe mit putchar
putchar(glob);
Peter Sobe
97
Eingabe von Zeichen mit scanf oder getchar
Standardeingabe scanf–Funktion, auch stdio.h einzubinden
char glob;
…
scanf(“%c“, &glob); // & bedeutet Adresse von glob
% leitet Formatangabe ein
c ist das char-Format
Einzugebende Zeichen werden mit ENTER abgeschlossen.
Weitere scanf-Anweisungen sollten mit \n beginnen.
Zeicheneingabe mit getchar
glob = getchar();
Peter Sobe
98
Arbeit mit Zeichen
Wie bei jedem Typ ist die Wertezuweisung definiert.
char x, y; .... x = y;
Es können auch Vergleiche mit char-Größen ausgeführt
werden. Hierbei wird auf den Zahlenwert des Zeichens
zurückgegriffen. Dies kann je nach char oder unsigned
char unterschiedlich sein:
if (x<y) ...
Es existieren Umwandlungsfunktionen:
z.B. i=atoi(&c); (char c; int i)
Die Funktion atoi bietet eine ascii-to-int Konvertierung.
Peter Sobe
99
Zeichenketten
Zeichenketten existieren in C nicht als eigener Typ. Es
können nur Zeichenkettenkonstanten z.B. zur Ausgabe
benutzt werden.
Eine Zeichenkette kann aber in C über ein
Feld von Zeichen ( char zk[20]; ) simuliert werden. Für
Zeichenketten stehen eine Reihe von Funktionen, wie z.B.
strcpy zur Verfügung. Diese befinden sich in string.h .
Konvention:
Werden diese Funktionen auf char-Felder angewandt,
muss das letzte Zeichen immer ein Nullzeichen \0 sein!
Peter Sobe
100
Zeichenkettenkonstante
Zeichenketten – Konstanten werden stets in DoppelApostroph eingeschlossen:
“Das ist eine nette Zeichenkette!“
Leerzeichen sind signifikant!
Wird eine solche Zeichenkettenkonstante z.B. einem charFeld zugewiesen, wird automatisch das Abschlusszeichen '\0' angehängt.
char zk[40] = “Das ist eine nette Zeichenkette!“ ;
// ergibt: zk={'D', 'a', 's', ' ', 'i', 's', 't', …, 'k', 'e', 't', 't', 'e', '!','\0'}
Eine solche Zuweisung ist nur so möglich!
Sonst muss sie über eine Funktion strcpy erfolgen.
Peter Sobe
101
Felder von Zeichen
Zeichenketten können in char-Feldern gespeichert
werden! Die Zuweisung erfolgt über den
Copykonstruktor:
char zk[20] = “Das ist eine Kette!“ ;
oder z.B.:
char w[5]={‘a‘,‘b‘,‘c‘,‘d‘,‘\0‘};
Ausgabe von Zeichenketten erfolgt über printf:
printf(“Ausgabe: %s“, zk);
s (string) ist das Format für Zeichenkette; diese muss
durch \0 abgeschlossen sein!
Peter Sobe
102
Eingabe von Zeichenketten
erfolgt über scanf:
scanf(“%10s“, zk);
• s (string) ist das Format-Umwandlungszeichen
für eine Zeichenkette;
• 10 ist die maximale Länge der zu übernehmenden Zeichen
• Eingabe wird durch ENTER abgeschlossen!
Achtung: Ein eingegebenes Leerzeichen beendet
bei %s die zu übernehmenden Zeichen !
scanf(“%[^\n]“, zk);
liest die Zeichen bis zum Zeilenumbruch in die
Zeichenkette ein
Peter Sobe
103
Standardfunktionen für Zeichenketten
Voraussetzung: #include <string.h>
Kopieren / Zuweisen strcpy()
strcpy(ziel, quelle);
Kopiert die Zeichenkette des char-Feldes quelle auf das char-Feld ziel
und erzeugt ein Endezeichen ‘\0‘. Der Rückkehrwert von strcpy ist vom
Typ char *, d.h. ein Zeiger auf die Kette „ziel“.
Verketten von Zeichenketten strcat()
strcat(ziel, quelle);
// ziel=ziel°quelle
Kettet die Zeichenkette des char-Feldes quelle an das char-Feld ziel.
Der Rückkehrwert von strcat ist vom Typ char *, d.h. ein Zeiger auf
die Kette „ziel“. Damit läßt sich auch ein Funktionsaufruf so gestalten:
strcpy(text1, strcat(“Das ist “,“ eine Verkettung“));
Peter Sobe
104
Standardfunktionen für Zeichenketten
Vergleichen von Zeichenketten strcmp()
strcmp(text1, text2);
Vergleicht die Zeichenkette des char-Feldes text1 mit der
Zeichenkette des char-Feldes text2 lexikographisch.
Der Rückkehrwert von strcmp ist vom Typ int.
Ergebnis :
< 0 : text1 less than text2
==0 : text1 identical to text2
> 0 : text1 greater than text2
Merke:
Verglichen wird anhand des Zahlencodes der Zeichen !!
Peter Sobe
105
Standardfunktionen für Zeichenketten
Länge einer Zeichenkette strlen()
int Laenge;
Laenge=strlen(kette);
Der Rückkehrwert ist die Länge der Zeichenkette, d.h. die Anzahl
der Zeichen ohne das Abschlusszeichen ‘\0‘.
In der Standardbibliothek string.h gibt es noch zahlreiche weitere
Funktionen für die Zeichenketten-Arbeit.
Peter Sobe
106
Inhalt
Inhalt:
4. Programmiersprache C
4.1 Programmaufbau in C
4.2 Basisdatentypen und einfache Anweisungen
4.3 Steuerfluss-Konstrukte
4.4 Arbeit mit indizierten Größen (Felder)
4.5 Arbeit mit Pointern
4.6 Arbeit mit Zeichenketten
4.7 Funktionen
4.8 Strukturen
4.9 Typen, Variable und Konstante
Peter Sobe
107
4.7 Funktionen
Von den Struktogrammen und Programmablaufplänen ist das Konzept einer
Prozedur bereits bekannt. Eine Prozedur fasst einen Teil des Algorithmus
zusammen und erlaubt, diesen Teil in einem übergeordneten Programm
aufzurufen. Die Prozedur wird durch Parameter mit Eingabedaten versorgt.
Ausgabedaten werden ebenfalls über Parameter vermittelt.
Ein Beispiel:
Fakultaet(w,f)
Eingabe x
f=1
Fakultaet(x,f)
Ausgabe f
w>1
ja
nein
i = 2 (1) w
f=f*i
Der Begriff Prozedur wird synonym zu Unterprogramm oder Subroutine
(engl.) verwendet.
In der Programmiersprache C werden Funktionen zur Realisierung von
Prozeduren benutzt.
R. Großmann, P. Sobe
108
Einsatzmöglichkeiten von Funktionen (1)
Funktionen erlauben,
 dem Programmcode hierarchisch zu strukturieren – ein Hauptprogramm
steuert dabei die Abfolge von Schritten, die einzelnen Schritte können
durch Funktionen realisiert werden
 mehrfach benötigte Programmteile nur einmal zu schreiben und
mehrfach aufzurufen
 Programmteile mehrmals leicht variiert auszuführen, gesteuert durch
Parameter
 Rekursive Algorithmen, indem eine Funktion sich selbst mit
abgeänderten Parametern aufruft
 zur Laufzeit zu entscheiden, welches Unterprogramm aufgerufen wird
(siehe Funktionen-Zeiger)
R. Großmann, P. Sobe
109
Einsatzmöglichkeiten von Funktionen (2)
Funktionen erlauben,
 Die Benutzung vorgefertigter Funktionen einer Standardbibliothek, z.B.
für Ein- und Ausgabe, für Zeichenkettenverarbeitung usw.
 Zusammenstellung und Sammeln von Hilfs-Funktionen, die ein
Programmierer persönlich oft benutzt
R. Großmann, P. Sobe
110
Funktionsdefinition (1)
Allgemeines Prinzip:
Rückgabetyp Funktionsname (Parameterliste)
{
Block mit
Deklarationen und
Anweisungen
return ausdruck ;
}
R. Großmann, P. Sobe
111
Funktionsdefinition (2)
Eine Funktion wird wie folgt definiert:
Rückgabetyp Funktionsname ( Parameterliste )
{
…
return …;
}
Durch den Funktionsnamen wird die Funktion
an anderer Stelle aufgerufen.
Die Parameterliste enthält eine durch Komma getrennte Folge von
Parametern, jeweils mit Typangabe und Variablenname.
Eine Funktion kann einen Wert mit dem angegebenen Rückgabetyp
zurückgeben. Dazu muss in der Funktion return Wert ausgeführt werden.
R. Großmann, P. Sobe
112
C-Programme als Folgen von Funktionen
Ein C-Programm stellt eine Folge von Funktionsdefinitionen
dar, wobei eine Funktion als Hauptprogramm (main)
gekennzeichnet sein muss. In den Funktionen befinden sich
wiederum Aufrufe anderer Funktionen. Funktionsdefinitionen
können in C nur global sein und dürfen nicht im Block einer
anderen Funktion bzw. des Hauptprogramms stehen.
R. Großmann, P. Sobe
113
Funktionen in C (1)
Beispiel 1: Funktion mit float-Rückkehrwert (return!)
float max(float a, float b)
{ if (a>b) return a;
else return b;
}
Beispiel 2: Funktion ohne Rückkehrwert
void druck(float x)
{ printf(„\n x=%f“,x);
}
Beispiel 3: Hauptprogramm mit Funktions-Aufruf
void main( )
{ float c=4.0f, d=5.99f;
druck(max(c,d));
}
R.Großmann, P.Sobe
114
Funktionen in C (2)
Beispiel mit Ausgabeparameter in der Parameterliste:
void fakultaet(int x, double *w)
{
int i;
*w=1.0;
for (i=2; i<=x; i++)
*w = *w * (double)i;
}
Aufruf der Funktion:
int i; double y;
for (i=0;i<40;i++)
{ fakultaet(i,&y);
printf("i=%03d, fak(i) =%.0lf \n",i,y);
}
R. Großmann, P. Sobe
115
Funktionen in C (3)
Benennung einer Funktion:
Funktionsnamen unterliegen den gleichen Einschränkungen wie
Variablennamen. Beispielsweise darf keine Ziffer am Beginn eines
Funktionsnamen stehen. Groß- und Kleinschreibung wird
unterschieden, d.h. Myfun() und myfun() sind zwei unterschiedliche
Funktionen.
Parameter:
Eine durch Komma getrennte Liste einzelner Typbezeichner und
Variablenbezeichner beschreiben die Parameter. Die als Parameter
angegeben Variablen sind innerhalb des Prozedurkörpers gültig und
überlagern gleichnamige globale Variablen. Die Parameter werden
bei Aufruf der Funktion auf diese Variablen kopiert (Call by Value).
Parameter, die als Ausgabe einer Funktion agieren, sind als Zeiger
zu übergeben (Call by Reference). Dann wird der Zeiger kopiert, die
Änderung des Werts erfolgt auf dem Originalspeicherplatz.
R. Großmann P. Sobe
116
Funktionen in C (4)
Bei einer C-Funktionsdefinition stellen die Parameter quasi Platzhalter dar.
Für diese Parameter werden dann beim Funktionsaufruf Argumente
eingesetzt. Die Anzahl , die Reihenfolge und der Typ von Argumenten beim
Funktionsaufruf muss immer mit Anzahl, Reihenfolge und Typ der
Parameter bei der Funktionsdefinition übereinstimmen.
Felder und Zeichenketten werden als Zeiger übergeben. Dabei wird
ausgenutzt, dass der Feldbezeichner ein Zeiger auf das erste Element ist.
void ausgabe_zeile(char *postitionsstring, int x, int y, float p)
{
printf(“Luftdruck an Position %s (%d,%d): %f \n“,
postionsstring, x, y, p);
}
…
ausgabe_zeile(“Zugspitze“, 51, 13, 998.17);
R. Großmann, P. Sobe
117
Funktionen in C (5)
Rückgabewert:
Funktionen können einen Wert zurückliefern.
Das erlaubt
Benutzung in Zuweisungen ...
wert = myfun(a,b); // myfun liefert einen Wert zurück
Benutzung einer Funktion als Parameter
einer anderen Funktion …
printf("Der Funktionswert an der Stelle %f ist %f \n",
x, fun(x) );
Wird die Angabe des Rückgabetyps weggelassen, nimmt der
Compiler standardmäßig integer an.
Funktionen, die keinen Wert zurückgeben sollen, werden mit den
Rückgabetyp void gekennzeichnet.
R. Großmann, P. Sobe
118
Funktion – Typ des Rückkehrwertes
Bei einer C-Funktionsdefinition muss vor dem Funktionsnamen ein
Typ (des Rückkehrwertes) angegeben werden. Dieser Typ kann ein
in C bekannter Standardtyp, wie z.B. int, unsigned long, char ,
double, ... sein.
Aber auch Zeigertypen, wie z.B. float *, int *, char *, ... sind zulässig.
Es ist darauf zu achten, dass der Typ des Ausdrucks in der returnAnweisung mit dem Typ des Rückkehrwertes kompatibel ist.
Wird ein Zeigerwert zurückgegeben, darf die Adresse nicht auf eine
lokale Variable der Funktion zeigen.
R.Großmann, P. Sobe
119
Funktionen mit Feldern als Parameter
Bislang wurde nach Eingabe- und Ausgabeparametern
unterschieden:
 Eingabeparameter – in der Parameterliste als Variable
(Call-by-Value)
 Ausgabeparameter – als Rückkehrwert, oder als Zeiger
in der Parameterliste (Call-by-Reference)
Bei Funktionen, die Felder (ein- oder mehrdimensional) als
Parameter haben, ist in C/C++ eine Besonderheit zu beachten. Bei
der Behandlung des Zeigerkonzeptes wurde festgestellt, dass die
Namen von Feldern wie Zeiger zu behandeln sind. Aus diesem
Grund wird bei Feldern nicht nach Eingabeparametern bzw.
Ausgabeparametern unterschieden, da ohnehin die Adressen der
Felder übergeben werden. In der Parameterliste wird ein Feld durch
den Typ der Elemente, den Namen und ein Paar eckige Klammern
gekennzeichnet, oder alternativ als Zeiger auf den Typ der
Feldelemente.
R.Großmann, P. Sobe
120
Beispiel mit eindimensionalen Feldern als Parameter
Funktion zur Berechnung des arithmetischen Mittels eines
Feldes von float-Zahlen mit n Elementen:
Funktionsdefinition:
float amittel(int n, float feld[ ]) // auch float *feld möglich
{ int i;
float s=0.0f;
for (i=0;i<n;i++)
s=s+feld[i]; // auch *(feld+i) möglich
return s/(float)n;
}
Beachte: bei eindimensionalen Feldern kann ein leeres
Klammerpaar [ ] angegeben werden.
R.Großmann, P. Sobe
121
Beispiel mit eindimensionalen Feldern als Parameter
Funktionsaufruf:
void main()
{ int dim;
float mittel,messwerte[100];
// Anzahl dim und Messwerte werden
// durch Einlesen belegt
mittel=amittel(dim, messwerte) ;
printf(„\narithm.Mittel=%f“,mittel);
}
R.Großmann, P.Sobe
122
Beispiel mit mehrdimensionalen Feldern als
Parameter
Funktion zum Transponieren einer quadratischen Matrix mit n²
Elementen des Typs integer.
Funktionsdefinition:
void trans(int n, int ma[ ][10])
{ int i,j,h;
for (i=0;i<n;i++)
for (j=0;j<i;j++)
{ h=ma[i][j]; ma[i][j]=ma[j][i]; ma[j][i]=h; }
}
Beachte: bei mehrdimensionalen Feldern müssen nach dem leeren ersten
Klammerpaar [ ] (Anzahl der Zeilen) unbedingt die nachfolgenden
Klammerpaare mit Konstanten, die die genaue Anzahl für diese Dimension
darstellen (z.B. genaue Anzahl der Spalten usw.) angegeben werden.
Die Anzahl bezieht sich auf die Deklarationsgrenzen des übergebenen
Argumentes, nicht auf die möglicherweise geringere Anzahl benutzter
Elemente!
R.Großmann, P. Sobe
123
Beispiel mit mehrdimensionalen Feldern als
Parameter
Funktionsaufruf:
void main()
{ int j,i,m; int x[10][10];
printf("\nm="); scanf("%d",&m); // m<=10
//Belegung der Matrix x
for (i=0;i<m;i++)
for (j=0;j<m;j++)
{printf("\nx[%d][%d]=",i,j);scanf("%d",&x[i][j]);
};
//Transponieren
trans(m,x);
for (i=0;i<m;i++)
for (j=0;j<m;j++) printf("\nx[%d,%d]=%d",i,j,x[i][j]);
}
R.Großmann, P. Sobe
124
Funktionszeiger
In C/C++ können Funktionen über Zeiger, die die Aufruf-Adresse der
Funktion enthalten, aufgerufen werden. Ein Funktionszeiger wird
folgendermaßen deklariert:
typ (*f)(); f ist der Name der Funktion und typ der Typ des Rückkehrwertes.
Der Funktionszeiger muss aber unbedingt mit der Adresse einer definierten
Funktion belegt werden!
void main()
{
int i,dim; float mittel,messwerte[100];
float (*fk)(); //Deklaration eines Funktionszeigers fk
fk=&amittel; //Zuweisen der Aufrufadresse der definierten Funktion amittel
printf("\nAnzahl Werte="); scanf("%d",&dim);
for (i=0;i<dim;i++)
{ printf("\nwert[%d]=",i);
scanf("%f",&messwerte[i]);
}
mittel=(*fk)(dim, messwerte) ; //Aufruf der Funktion amittel
printf("\narithm.Mittel=%f",mittel);
} R.Großmann, P. Sobe
125
Funktionen als Parameter von Funktionen
In C/C++ müssen Funktionen, die als Parameter an andere Funktionen
übergeben werden sollen, in der Parameterliste als Funktionszeiger
gekennzeichnet werden. Zusätzlich zur normalen Funktionszeigernotation müssen zusätzlich die Typen der Parameter dieser zu
übergebenden Funktion aufgeführt sein.
In Ergänzung zur Deklaration als Funktionszeiger muss die
Parameterliste des Funktionszeigers die Typen der an die Funktion zu
übergebenden Parameter enthalten.
Beispiel: float (*f)(float)
das wäre der Funktionszeiger einer Funktion f(x), die einen floatParameter x hat und mit einem float-Wert zurück kommt (z.B. cos(x) ).
Anwendungsbereiche:
Funktionen müssen an Funktionen übergeben werden bei Berechnung
von beispielsweise
 Nullstellenberechnung von Funktionen
 bestimmten Integralen
 Interpolation von Funktionen
R.Großmann, P. Sobe
126
Beispiel für Funktionen als Parameter von
Funktionen
float funkt(float x)
{ return cos(x); }
float halb(float a,float b, float (*f)(float)) //Halbierungsverfahren zur Nullst.-ber.
{ float m;
m=(a+b)/2.0f;
while (fabs((*f)(m))>0.00001)
{ m=(a+b)/2.0f;
if ((*f)(m)*(*f)(b)<0.0f) a=m;else b=m;
}
return m;
}
void main()
{ float a,b,nullst;
printf("Halbierungsverfahren\n");
printf("Eingabe Intervall\na="); scanf("%f",&a);
printf("\nb="); scanf("%f",&b);
nullst=halb(a,b,funkt); //Übergabe der Funktion funkt
printf("\nNullstelle=%f\n",nullst);
}
R.Großmann, P. Sobe
127
Rekursive Funktionen (1)
In C/C++ können Funktionen rekursiv definiert werden. Das kann in
direkter oder indirekter Form geschehen. Bei einer direkten Rekursion
enthält die Funktionsdefinition einen Aufruf von sich selbst, während im
indirekten Fall eine andere Funktion aufgerufen wird, die wiederum, die
zu definierende Funktion ruft.
Da die Verwaltung aller lokalen Größen ohnehin durch den Compiler
in einem Runtime-Stack vorgenommen wird, muss der Programmierer
bei der Definition rekursiver Funktionen nichts besonderes beachten.
Allerdings müssen stets Anweisungen vorhanden sein, die den
rekursiven Aufruf begrenzen, um ein „Endlos-Aufrufen“ zu verhindern.
R.Großmann, P. Sobe
128
Rekursive Funktionen
Beispiel ( vgl. rekursive Algorithmen bei Struktogrammen –
FIBONACCI-Zahlen):
fibo(n)= fibo(n-1) + fibo(n-2)
Rekursionsabbruch fibo(1)=1 fibo(0)=1
int fibo(int n) //rekursive Definition
{ if (n<2) return 1;else return (fibo(n-1)+fibo(n-2)); }
void main()
{ int i=1,f=1;
printf("\nBerechnung der FIBONACCI-Zahlen im Intervall [0,100]");
printf("\n\n x fibo(x)\n------------");
while ((f=fibo(i))<=100)
{ printf("\n%2d %3d",i,f); i++; }
}
R.Großmann, P. Sobe
129
Funktionen - Zusammenfassung
 Eine Funktion besteht aus Rückgabetyp, Funktionsname, Parameterliste
und einem Anweisungsblock, der in geschweifte Klammern “{“, “}“
eingefasst wird.
 Rückgabetyp kann jeder gültige Typ sein, auch selbstdefinierte Typen,
Strukturen oder Zeiger.
 Die Anweisung return dient zur Rückgabe eines Wertes aus der
Funktion. Die Ausführung wird dann mit der Anweisung fortgesetzt, die
dem Funktionsaufruf folgt.
 Funktionen, die keinen Wert zurückgeben, erhalten den Rückgabetyp
“void“.
 Funktionen können wie Variable gehandhabt werden. Funktionen
können so als Parameter anderen Funktionen übergeben werden.
 Funktionen können sich selbst aufrufen: Rekursion
R. Großmann, P. Sobe
130
Inhalt
Inhalt:
4. Programmiersprache C
4.1 Programmaufbau in C
4.2 Basisdatentypen und einfache Anweisungen
4.3 Steuerfluss-Konstrukte
4.4 Arbeit mit indizierten Größen (Felder)
4.5 Arbeit mit Pointern
4.6 Arbeit mit Zeichenketten
4.7 Funktionen
4.8 Strukturen
4.9 Typen, Variable und Konstante
R. Großmann, P. Sobe
131
4.8 Strukturen in C
Aus C-Sicht stellen Strukturen eine Zusammenfassung von
Datenelementen unterschiedlichen Typs unter einem Namen dar.
Strukturen sind aus C++ Sicht Klassen (Typen) deren Elemente
öffentlich (public) sind.
Dadurch sind sie vergleichbar mit Feldern, die eine Zusammenfassung (Reihung) von Datenelementen gleichen Typs unter einem
Namen darstellen.
Der Zugriff auf Datenelemente ist allerdings unterschiedlich:
 bei Feldern über den Index hinter dem Feldnamen, z.B. feld[i]
 bei Strukturen durch den Datenelementnamen hinter dem
Strukturnamen und einem Punkt (“.“), z.B. Person.Name
R.Großmann, P. Sobe
132
Aufbau und Deklaration einer Struktur
Person
Name
struct Person
{
char Name[30];
char Vorname[30];
Vorname
int Geschlecht;
Geschlecht
Gehalt
float Gehalt;
};
Person ist der Strukturname (und damit wie ein Typ)
und Name, Vorname,... sind Komponentennamen.
R.Großmann, P. Sobe
133
Aufbau und Deklaration einer Struktur (1)
Deklaration von Struktur-Variablen
Da die Deklaration einer Struktur wie eine Typdefinition
wirkt, können Variable diesen Strukturtyps
folglich unter Nennung von struct Strukturname
deklariert werden.
Beispiel:
struct Person {...};
struct Person p1,p2;
Es wäre auch eine Kombination möglich:
struct Person {...} p1,p2;
R.Großmann, P. Sobe
134
Aufbau und Deklaration einer Struktur (2)
Deklaration von Struktur-Variablen mittels TypDefinition
typedef struct Person {...} person_t;
…
person_t p1,p2;
Ein per typdef geschaffener Typ kann wie ein
Standarddatentyp (int, float, usw.) zur Deklaration von
Variablen und zur Deklaration weiterer Strukturen
benutzt werden.
R.Großmann, P. Sobe
135
6.2 Wertebelegung von Strukturvariablen
Wertebelegung von Struktur-Variablen:
Eine Möglichkeit besteht in der Initialisierung bei der
Deklaration:
Beispiel: struct Person
p1={"Krause","Jens",1,3500.40},
p2={"Meier","Ines",0,4100.33};
oder es erfolgt eine Wertezuweisung an die
Komponenten:
strcpy( p1.Name , "Krause" );
p1.Gehalt = 3500.40;
R.Großmann, P. Sobe
136
6.3 Schachtelung von Strukturen
Strukturen können wieder Strukturen enthalten
Beispiel:
struct Datum
{ int tag;
char monat[10];
int jahr;
};
struct Person
{ char name[30];
char vorname[20];
struct Datum beginn; // Struktur in Struktur
int geschlecht;
};
R.Großmann, P. Sobe
137
Schachtelung von Strukturen
Die Schachtelung darf aber nur in der angegebenen Weise
erfolgen, d.h. man darf eine Struktur-Deklaration nicht direkt in
eine weitere Struktur aufnehmen, sondern ausschließlich über
eine Strukturvariable.
Zugriff auf geschachtelte Strukturen:
z.B.: struct person p1; und person enthält die geschachtelte
Struktur struct datum beginn;
p1.name aber p1.beginn.monat
R.Großmann, P. Sobe
138
Felder von Strukturen
Strukturen können als Elemente in Feldern gereiht werden
Beispiel:
struct Person
{ char name[30];
char vorname[20];
struct Datum beginn;
int geschlecht;
};
struct Person mitarbeiter [5] ;
Zugriff: mitarbeiter[1].name
mitarbeiter[1].beginn.monat
R.Großmann, P. Sobe
139
Ein Beispiel mit Strukturen (1)
Auf Übungsblatt 8, Teilaufgabe 1 (für Fernstudenten Ü-Blatt 3)
waren Messwerte und Orte im Speicher zu organisieren, zu
sortieren und auszugeben. Bislang hatten wir zwei
unabhängige Felder benutzt:
float werte[MAX_WERTE];
char orte[MAX_WERTE] [MAX_STRLEN];
Hier bietet sich an, Wert und Ort in einer Struktur
zusammenzufassen.
struct messung {
float wert;
char ort[MAX_STRLEN];
};
…
struct messung m[MAX_WERTE]; // Feld
struct messung zws_messung; // Zwischenspeicher für Tausch
R.Großmann, P. Sobe
140
Ein Beispiel mit Strukturen (2)
i=0; ende=0; n_werte=0;
while(!ende)
{ if (i<MAX_WERTE)
{ printf("Eingabe Messwert Ort [%d]:",i);
gets(eingabestring);
sscanf(eingabestring,"%f", &m[i].wert);
if (m[i].wert==999.0)
ende=1;
else
{ sscanf(eingabestring,"%s",dummystring);
strcpy( m[i].ort, &eingabestring[strlen(dummystring)]);
i = i + 1;
n_werte ++;
}
}
else // Gesamtanzahl der Messwerte erreicht
ende = 1;
}
R.Großmann, P. Sobe
141
Ein Beispiel mit Strukturen (3)
Zuweisung von Strukturen, hier im Kontext des Tauschs der
Elemente
….
if (tausch)
{ // Umspeichern der Strukturen
zws_messung = m[i];
m[i] = m[i+1];
m[i+1] = zws_messung;
}
Eine Zuweisung von Strukturvariable beinhaltet das Kopieren aller
Komponenten auf die Zielstruktur. Hier werden auch die
Zeichenketten kopiert.
R.Großmann, P. Sobe
142
Zuweisen, Kopieren von Strukturvariablen
Bei der Zuweisung von Strukturen a = b werden immer alle
Elemente von b nach a kopiert.
Das ist gleichbedeutend mit Kopieren der Speicherinhalte, die
eine Strukturvariable einnimmt.
Die Länge einer Struktur (hier kann der Typ oder die
Strukturvariable benutzt werden) wird mit dem Operator
size_t sizeof (strukturname);
ermittelt. Vor dem C99-Standard wurde die Größe noch zur
Übersetzungszeit ins Programm eingesetzt. Spätere Compiler
erlauben, die Größe dynamisch zur Laufzeit zu bestimmen.
R.Großmann, P. Sobe
143
sizeof - Operator
size_t sizeof (name);
wobei name eine Variable, eine Struktur, ein Typ oder eine Feld sein
kann. Namen für Felder werden hier aber nicht wie die Anfangsadresse
auf das erste Element gewertet, sondern stehen für den
Gesamtspeicherplatz des Feldes.
Beispiele:
#define MAX_STRLEN 120
struct messung {
float wert;
char ort[MAX_STRLEN];
};
struct messung mvar, m[10];
int l1 = sizeof(messung); // ergibt 124
int l2 = sizeof(mvar);
// ergibt auch 124
int l3 = sizeof(m);
// ergibt 1240
R.Großmann, P. Sobe
144
Zeiger auf Strukturen (1)
Es können Zeiger auf Strukturen erklärt werden
Beispiel:
struct datum
{ int tag; char monat[10]; int jahr;
} d1,d2;
struct datum *zeig;
zeiger = &d1; // Adresse der Strukturvariablen d1
(*zeiger).tag //Zugriff auf Komponente
zeiger->tag //Zugriff auf Komponente
beide Schreibweisen sind identisch!
R.Großmann, P. Sobe
145
Zeiger auf Strukturen (2)
Das Kopieren von Strukturen, ausgehend von Zeigern:
Beispiel:
struct datum
{ int tag; char monat[10]; int jahr;
} d1,d2;
datum *zeiger1 = &d1; // Adresse der Strukturvariablen d1
datum *zeiger2 = &d2; // Adresse d2
…
(*zeiger2) = (*zeiger1); // ist eine Möglichkeit
memcpy(zeiger2,zeiger1,sizeof(datum));
// die andere Möglichkeit
R.Großmann, P. Sobe
146
Zeiger auf Strukturen an Funktionen übergeben
Es können Zeiger auf Strukturen als Parameter von
Funktionen auftreten.
Beispiel:
struct datum
{ int tag; char monat[10]; int jahr;
} d[5]={{7,“April“,1981},{12,“Juni“,1979},
{20,“Mai“,1980},{4,“Juni“,1974},{0,“ “,0}};
int finde(int j,struct datum *p)
{ int i=0; while ((p->tag)!=0){if ((p->jahr)==j)
return (p->tag);else p++;}; return 0;}
Aufruf: finde(1980,d);
R.Großmann, P. Sobe
147
Inhalt
Inhalt:
4. Programmiersprache C
4.1 Programmaufbau in C
4.2 Basisdatentypen und einfache Anweisungen
4.3 Steuerfluss-Konstrukte
4.4 Arbeit mit indizierten Größen (Felder)
4.5 Arbeit mit Pointern
4.6 Arbeit mit Zeichenketten
4.7 Funktionen
4.8 Strukturen
4.9 Typen, Variable und Konstante
R. Großmann, P. Sobe
148
4.9 Typen, Variable und Konstante
Überblick:
 Gültigkeitsbereiche von Variablen, Sichtbarkeit
 Speicherklassen
 Type-Umwandlungen durch Typecasts
 Präprozessorkonstanten und Makro-Funktionen durch #define
P. Sobe
149
Gültigkeitsbereiche (1)
Variablen können verschiedene Gültigkeitsbereiche besitzen:
lokale Gültigkeit und globale Gültigkeit
Lokale Gültigkeit
Variablen können nur in der Funktion benutzt werden, in der sie
deklariert werden. Eine lokale Gültigkeit kann auf die main-Funktion,
aber auf jede andere Funktion, oder auch auf Verbundanweisungen
bezogen sein.
Lokale Gültigkeit innerhalb der main()-Funktion: Die Variablen sind
nur innerhalb der main-Funktion sichtbar. Andere Funktionen
(Prozeduren) können nicht direkt auf diese Variablen zugreifen. Bei
Bedarf muss der Wert der Variablen den Funktionen über Parameter
vermittelt werden.
P. Sobe
150
Gültigkeitsbereiche (2)
Beispiel zur lokalen Gültigkeit innerhalb der main()-Funktion:
int berechne(int arg)
{
// b, c sind hier nicht sichtbar, a wird über arg vermittelt
return arg*2;
}
main()
{
int a, b, c;
…
b = berechne(a);
…
}
P. Sobe
151
Gültigkeitsbereiche (3)
lokale Gültigkeit in einer Funktion (Prozedur): Nur der Code
innerhalb der Funktion kann diese Variable benutzen. Wird aus der
Funktion zurückgesprungen, sind die Variablen nicht mehr sichtbar. Ihr
Speicherplatz wird freigegeben und für andere Zwecke verwendet.
double fakultaet(int arg) {
double f=1.0;
int i; // ist nur lokal gültig
if (arg<2) return 1.0;
for (i=2;i<=arg;i++) f = f * (double)i;
return f;
}
main()
{
double y = fakultaet(42);
printf(“%lf “, y);
printf(“%d“,i); // Fehler: i ist hier nicht sichtbar
}
P. Sobe
152
Gültigkeitsbereiche (4)
lokale Gültigkeit in einer Funktion (Prozedur) …
Achtung: Bei mehrmaligem Aufruf einer Funktion bleibt der Wert einer
lokalen Variablen i.A. nicht gespeichert (Ausnahme durch spezielle
Speicherklasse). Die Variable verliert zwischenzeitlich ihre Gültigkeit und
wird bei jedem neuen Eintritt in die Funktion neu erzeugt.
Sichtbarkeit und Überdeckung lokaler Variable:
void f1(int *a) { int x=5; *a = *a+f2(&x); }
void f2(int *a) { int x=77; *a = *a+x; }
Die Variable x in f2 überdeckt x aus f1. Innerhalb f2 wird mit dem Wert
x=77 gearbeitet. Die Variable x innerhalb f1 bleibt bestehen, ist aber
nicht sichtbar, wenn f2 abgearbeitet wird
P. Sobe
153
Gültigkeitsbereiche (5)
Globale Gültigkeit:
Globale Variablen sind über alle Funktionen hinweg gültig. Eine
Ausnahme ist die lokale Überdeckung durch gleich benannte Variablen
in Funktionen oder Verbundanweisungen.
Durch globale Variable können Daten zu und von Funktionen vermittelt
werden:
Vorteile: bequem für Programmierer;
schnell, da kein Kopieraufwand bei Aufruf der Funktion
Nachteil: Die Funktionsdeklaration enthält keine Information, welche
globalen Daten zur Eingabe und/oder Ausgabe benutzt werden.
Man verliert schnell den Überblick und kann nicht mehr einschätzen, ob
der Aufruf von Funktionen eventuell unerwünschte Nebenwirkungen hat.
→ Globale Variablen sehr sparsam verwenden!
P. Sobe
154
Gültigkeitsbereiche (6)
Beispiel für globale Gültigkeit:
int vermoegen=0; // globale Variable
void erbschaft( int betrag)
{ vermoegen = vermoegen + betrag;
zahle_erb_steuer(betrag);
}
void zahle_erb_steuer(int erb_betrag)
{ vermoegen = vermoegen
– (int)( (float)erb_betrag * (float) E_STEUERSATZ/100.0);
}
main()
{ ...
vermoegen = X;
erbschaft(Y);
if (vermoegen >= 1000000) printf(“ Ich bin reich! \n“);
}
P. Sobe
155
Speicherklassen (1)
C besitzt vier Schlüsselworte, um die Speicherklasse von
Variablen zu definieren. Sie teilen dem Übersetzer mit, wie
eine Variable zu speichern ist.
auto – Standard für lokale Variablen
register – wie auto, Variable wird vorrangig in einem
Prozessorregister gespeichert (anstatt im Hauptspeicher)
…
auto und register nur für lokale Variablen
static – Variable behält den Wert auch zwischen
Funktionsaufrufen
extern – Variable ist bereits an anderer Stelle deklariert
und soll nur als Bezeichner noch einmal eingeführt
werden … sinnvoll bei mehreren Quelletextdateien.
P. Sobe
156
Speicherklassen (2)
Beispiel für static:
int zaehlen()
{ static int zahl=0;
zahl++;
return zahl;
}
main()
{
int x, ergebnis;
for ( x=1;x<47;x++)
ergebnis = zaehlen();
printf(“Das Ergebnis lautet %d\n“, ergebnis);
}
P. Sobe
157
Typecasts
Der Übersetzer wandelt Typen von einem in einen anderen um,
wenn es notwendig wird. In einigen Fällen werden Warnungen
generiert, falls es zu Genauigkeitsverlust kommt oder
Zuweisungen mit unterschiedlichem Wertebereich
angewiesen werden.
Man kann durch Typcasts aber auch eine Typumwandlung
explizit anweisen.
Typischer Fall:
int a=3,b=5;
float x_verhaeltnis_ab = a/b; // ergibt Warnung und Ergebnis 0
float y_verhaeltnis_ab = (float) a/b; // ergibt Ergebnis 0.0
float z_verhaeltnis_ab = (float) a/(float) b; // ergibt 0.6
P. Sobe
158
Präprozessor-Konstanten (1)
Für Parameter, die zur Übersetzungszeit festgelegt werden, bieten
sich Präprozessorkonstanten an.
Allgemeine Form:
#define NAME WERT
• NAME ist der Konstantenname, der im Programm benutzt wird
• WERT ist dabei eine Zeichenkette, die aber auch einen Zahlenwert
repräsentieren kann.
Vor den eigentlichen Übersetzen wertet der Präprozessor die #define
Anweisungen aus, und setzt überall dort, wo NAME als Bezeichner im
Quellcode steht, die Zeichenkette WERT ein
P. Sobe
159
Präprozessor-Konstanten (2)
Beispiel:
//#define STADT „Dresden“
#define STADT „Cuxhafen“
#define STEUERSATZ 19
#define PI 3.14159265
//#define BEGRUESS_STRING “Sehr geehrtes Publikum“
#define BEGRUESS_STRING “Liebe Seeleute und Sportschiffer“
//#define BEGRUESS_STRING “Liebe Parteigenossen“
#define RICHTUNG “gesenkt“
//#define RICHTUNG “erhoeht“
printf(“%s, unabhaengig von der Zahl Pi mit dem Wert %f gilt in %s“
“ ein Steuersatz von %d Prozent.\n“,
BEGRUESS_STRING,PI,STADT,STEURSATZ);
printf(“Wir stehen dafuer, dass der Steuersatz in %s %s wird!\n“,
STADT, RICHTUNG);
P. Sobe
160
Präprozessor-Konstanten (3)
Beispiel: Maximale Anzahl von Pixeln in einem Bild
#define MAX_HORIZ 4000
#define MAX_VERT 4000
struct { char r,g,b;} pixel;
pixel bild[MAX_VERT][MAX_HORIZ];
for(int v=0;v<MAX_VERT;v++)
for(int h=0;h<MAX_HORIZ;h++)
bild[v][h] = …;
P. Sobe
161
Präprozessor-Makros (1)
Der Präprozessor kann auch kleine Makro-Funktionen mit
Parametern in dem Code ersetzen.
#define ABS(x) ((x)<0?-(x):(x))
….
float a,b;
int x,y;
….
b = ABS(a); // wird ersetzt durch: b = a<0?-a:a;
y = ABS(x); // wird ersetzt durch: y = x<0?-x:x;
Vorteil: Man kann Details in Funktionen verbergen. Der Aufwand
eines Funktionsaufrufs wird aber zur Laufzeit vermieden. Programme
laufen dadurch schneller!
P. Sobe
162
Präprozessor-Makros (2)
Unterschied Makros gegenüber echten C-Funktionen:
 Makro-Funktionen werden durch Ersetzung im Quelltext realisiert.
 C-Funktionen werden zur Laufzeit aufgerufen und als ganzes
ausgeführt.
Wenn Operationen innerhalb und außerhalb der Makros/Funktionen
verschiedene Prioritäten aufweisen, kann sich ein unterschiedliches
Verhalten ergeben, je nachdem ob man eine Makro-Funktion oder
eine echte C-Funktion benutzt.
Beispiel:
#define F(x,y) (x)+(y)
…
a=1;
b=2;
c=3;
int f(int x, int y) { return x+y; }
…
a=1;
b=2;
c=3;
d=F(a,b)*c; // d = a+b*c;
// d = 1+2*3 =
d1=f(a,b)*c; // f(1,2) = 3
// d = 3* c = 9
P. Sobe
163
Herunterladen