forked from pittlerf/ckurs2017
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfunktionen.tex
192 lines (168 loc) · 8.22 KB
/
funktionen.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
\section{Funktionen}
Es gibt viele Quelltextabschnitte, die wiederholt benutzt werden.
Es ist sinnvoll, diese Abschnitte im Quelltext nicht ständig zu wiederholen.
Das erhöht einerseits die Lesbarkeit des Quelltextes und macht andererseits Code weniger fehleranfällig, da der Abschnitt nur einmal getestet werden muss.
Dafür existiert das Konzept von Funktionen.
Eine Funktion haben wir schon kennen gelernt, nämlich die Funktion \verb|main|.
\subsection{Funktionen Prototypen}
In \texttt{C} sind Funktionen Variablen sehr ähnlich.
Eine Funktion kann wie folgt deklariert werden\index{Funktion}
\begin{lstlisting}
Rueckgabetyp Funktionsname(Parameterliste);
\end{lstlisting}
Man spricht von einem sogenannten \emph{Funktionsprototypen}.\index{Funktion!Prototyp}
Die Parameterliste besteht aus durch Kommata getrennten Variablendeklarationen
\begin{lstlisting}
Typ1 parameter1, Typ2 parameter2, ...
\end{lstlisting}
Die Parameterliste kann auch leer sein.
Der Rückgabetyp kann jeder \texttt{C} Typ und jeder selbst definierte Typ sein.
Bei streng strukturierten Programmiersprachen wie \texttt{C} werden der Funktionsname, die Parameterliste und der Rückgabetyp zusammen als \emph{Signatur} der Funktion bezeichnet.
\subsection{Funktionen Definition}
\index{Funktion!Definition}
Die Definition einer Funktion muss dann natürlich einen Block von Anweisungen enthalten, also
\begin{minipage}{\linewidth}
\begin{lstlisting}[caption={Funktionen Prototyp}, belowcaptionskip=0.3em]
Rueckgabetyp Funktionsname(Typ1 parameter1, Typ2 parameter2,
Typ3 parameter3, ...)
{
Rueckgabetyp x;
Anweisung1;
Anweisung2;
...;
return x;
}
\end{lstlisting}
\end{minipage}
Die in der Parameterliste deklarierten Variablen sind dann innerhalb dieses Blocks definiert und unter ihrem Namen sichtbar.
Außerdem sind alle \emph{global} deklarierten Variablen im Funktionsblock sichtbar.
Für den Funktionsnamen gelten die gleichen Regeln, wie für Variablennamen.
Als Beispiel betrachten wir eine Funktion, die die Fakultät einer ganzen Zahl berechnet:
\begin{minipage}{\linewidth}
\begin{lstlisting}
unsigned long int Fakultaet(const unsigned int zahl)
{
unsigned long int fak = 1;
for (unsigned long int i = 2; i <= zahl; i++)
{
fak *= i;
}
return fak;
}
\end{lstlisting}
\end{minipage}
Der Rückgabetyp ist als \verb|unsigned long int| gewählt, da die Fakultaet immer positiv ist, aber auch sehr groß werden kann.
Funktionen sollten wohldefinierte Unterprobleme lösen.
Ihr Umfang hängt natürlich von der Komplexität dieser Unterprobleme ab.
Man sollte trotzdem versuchen, dass Funktionen nicht zu viel Quelltext enthalten.
Sonst kann man mit sehr großer Wahrscheinlichkeit den Quelltext noch weiter aufspalten.
\subsection{Aufruf von Funktionen}
\index{Funktion!Aufruf}
Aufgerufen werden Funktionen dann im Prinzip wie folgt
\begin{lstlisting}
Ergebnistyp Ergebnis = Funktionsname(Uebergabeliste);
\end{lstlisting}
Die \verb|Uebergabeliste| enthält dabei Variablen aus dem aufrufenden Codeabschnitt oder konstante Ausdrücke.
Dabei muss die Reihenfolge in der \verb|Uebergabeliste| genau mit der in der \verb|Parameterliste| in der Funktionsdeklaration übereinstimmen.
Auch die Typen in \verb|Uebergabeliste| müssen mit denen in der \verb|Parameterliste| übereinstimmen.
Der \texttt{C} Compiler überprüft dies strickt und bricht die Übersetzung ab, falls es Abweichungen gibt.
Obige Funktion zur Berechnung der Fakultät kann wie folgt im Hauptprogramm aufgerufen werden:
\begin{minipage}{\linewidth}
\begin{lstlisting}
#include <stdio.h>
int main()
{
int n = 20;
unsigned long int result; // Variable fuer das Ergebnis
result = Fakultaet(n); // Funktionenaufruf
// oder
result = Fakultaet(3); // Funktionenaufruf alternativ
printf("%lud", result); // Ausgabe des Ergebnisses
return 0;
}
\end{lstlisting}
\end{minipage}
\subsection{Typenüberprüfung und sinnvolle Eingabewerte}
Wie schon oben erwähnt, führt der \texttt{C} Compiler für Aufrufe einer Funktion eine Typenüberpüfung durch, man muss jedoch bei elementaren Datentypen vorsichtig sein, da implizite casts zu unerwarteten Ergebnissen führen können.
Der Aufruf der Funktion in der Form
\begin{lstlisting}
unsigned long int result;
double x = -2.0;
result = Fakultaet(x);
\end{lstlisting}
wird leider vom Compiler\footnote{getestet unter \texttt{gcc 5.4.0} mit \texttt{-Wall -Wpedantic}} problemlos übersetzt.
Die implizite Umwandlung macht aus \texttt{double} den einfachsten, ganzzahligen und vorzeichenbehafteten Datentyp, \texttt{int}.
Dies führt wiederum dazu, dass nach einer weiteren Umwandlung zu \texttt{unsigned int}, die Zahl $4294967294$ übergeben wird und das Programm in einer Endlosschleife endet.
Der Compiler kann allerdings nicht überprüfen, ob die Eingabe sinnvoll ist.
Im Fall \verb|Fakultaet| bekommt die Funktion nur einen Parameter vom Typ \verb|unsigned int| übergeben.
An dieser Stelle sollte man sich als Programmierer fragen, ob jede mögliche Eingabe zu einer sinnvollen Ausgabe führt.
Das ist offensichtlich dann nicht mehr der Fall, wenn die funktionsinterne Variable \verb|fak| aus dem Wertebereich für \verb|long unsigned int| herausläuft.
Man kann sich leicht überlegen, für welche Werte von \verb|zahl| dies geschieht.
Eine solche Überprüfung würde auch den oben aufgeführten Fehler zumindest abfangen und die Endlosscheife beenden.
Optimalerweise sollte in der Funktion überprüft werden, ob \verb|zahl| im sinnvollen Wertebereich liegt.
Wenn dies nicht der Fall ist, sollte die Bearbeitung abbrechen, zum Beispiel so
\begin{lstlisting}
// pruefe, ob zahl im sinnvollen Wertebereich liegt
if(zahl > N) {
// falls nein, bricht die Ausfuehrung ab
exit(EXIT_FAILURE);
}
\end{lstlisting}
Die Funktion \verb|exit| ist in der Headerdatei \verb|stdlib.h| deklariert.
\verb|EXIT_FAILURE| (und \verb|EXIT_SUCCESS|) ist ebenfalls in \verb|stdlib.h| definiert.
\subsection{Rückgabetypen und \texttt{void} Funktionen}
\index{\texttt{void}}
Funktionen haben in \texttt{C} immer einen Rückgabetyp.
Hierbei sind alle \texttt{C} Typen und auch selbstdefinierte Typen möglich.
Auf den ersten Blick scheint dies zu bedeuten, dass immer nur skalare Größen zurückgegeben werden können.
Das stimmt aber nur für \texttt{C} Typen, und auch nur, wenn man Zeiger außen vor lässt.
Letzteres wird im nächsten Kapitel diskutiert werden.
Es gibt auch den Fall, dass Funktionen keine Rückgabewert liefern.
Für diesen Fall gibt es in \texttt{C} den \verb|void| Datentyp.
Eine \verb|void| Funktion kann beispielsweise wie folgt aussehen:
\begin{lstlisting}
void myFunction(...) {
// do something
return;
}
\end{lstlisting}
Diese Funktion kann dann wie folgt aufgerufen werden
\begin{lstlisting}
// some code
myFunction(...);
// more code
\end{lstlisting}
also ohne eine Zuweisung des Funktionswertes an eine entsprechende Variable.
\subsection{Funktionsaufrufe mit vielen Argumenten}
Wenn einer Funktion viele Argumente übergeben werden, so kann man mithilfe von Kommentaren verdeutlichen, welcher Wert, welchem Parameter entspricht.
\begin{myalertblock}{mehrzeiliger Funktionsaufruf}
Der Aufruf einer Funktion mit vielen Argumenten kann recht schnell schwer verständlich werden.
\begin{lstlisting}
void func(const int par1, const double par2,
const char par3, const int par4,
const double par5, const double par6,
const unsigned long int par7)
{
[...]
}
double x = 2.0;
double y = 4.5;
[...]
func(1, x, 'c', 3, y, 7.3, 8908123908);
\end{lstlisting}
Wir können diesen Aufruf übersichtlicher gestalten, indem wir den Aufruf ersten auf mehrere Zeilen aufspalten und zweitens, Kommentare einfügen, die den Parameter entweder beschreiben oder benennen.
\begin{lstlisting}
double x = 2.0;
double y = 4.5;
[...]
func(/* par1 */ 1,
/* par2 */ x,
/* par3 */ 'c',
/* par4 */ 3,
/* par5 */ y,
/* par6 */ 7.3,
/* par7 */ 8908123908);
\end{lstlisting}
Blickt man jetzt auf die Funktionssignatur und vergleicht mit dem Aufruf, sieht man viel schneller, ob man für die einzelnen Argumente die richtigen Datentypen sowie Werte übergibt.
\end{myalertblock}
\endinput