Teil A: Grundlagen strukturierter Programmierung
II. Fortgeschrittene Elemente der Programmierung
Rezept 105 – Gefüllte Tomaten
Zutaten:
1 ½ kg (8 grössere) Fleischtomaten 50 gr. Fett oder Margarine
1-1 ½ Tassen Wasser Salz
Für die Füllung
400 gr. Mageres Hackfleisch 2 mittelgrosse Zwiebeln 40 gr. (1 Handvoll) Reiskörner je 1 Bund Dill und Petersille
Zubereitung:
Tomaten waschen und einen flachen Deckel abschneiden. Das Fruchtfleisch mit einem Teelöffel aushöhlen und unter den Hackfleisch kneten, der wie Dolma-Rezept Nr. 1 zubereitet wird. Ausgehöhlte Tomaten mit Hackfleisch füllen, Deckel wieder aufsetzen und in einem Topf aufreihen. Mit Fettflocken belegen, mit Wasser begiessen und bei Mittelhitze zugedeckt 30-35 Minuten dünsten.
Eingabe (E)
Verarbeitung (V)
Ausgabe (A)
Teilrezept
12. Unterprogramme (Funktionen)
Ziele dieser Einheit:
- Aufgabenzerlegung in logisch abgeschlossene
Programmeinheiten zur Vermeidung überdimensionaler Programme.
- Erhöhung der Lesbarkeit und Erleichterung der Pflege langlebiger Programme („Legacy/Heritage Software“).
- Disziplinierung der Kommunikation zwischen
unterschiedlichen Programmeinheiten.
– Umfangreiche, komplexe Funktionalitäten führen oft zu Programmen, die zu lang und damit meist schwer verständlich werden.
– Gleiche Aufgabenstellungen, die an verschiedenen Stellen des Programms benötigt werden, werden wiederholt geschrieben.
– Ausweg: Lösung so lange in kleinere Teile zerlegen, dass nur noch leicht überschaubare Einheiten („Moduln“) übrig bleiben.
12.1 Motivation
12.2 Grundbegriffe
Prinzip: Teile und herrsche!
– Das Unterprogramm besteht dann aus:
• Programmkopf,
• Deklarationsteil,
• Unterprogramm-Körper („body“) als eigentliche Arbeitseinheit.
– Im Hauptprogramm: Aufruf(e) des Unterprogramms.
– Das Unterprogramm (UP) wird bei seinem Aufruf aktiv (erhält die Steuerung).
– Nach der letzten Anweisung ( „ } “) des UP erhält das Hauptprogramm (HP) die Steuerung wieder.
– UPe können selber UPe haben und eigene (lokale) Variablen besitzen. Diese sind für das HP unsichtbar
– (Globale) Variable des Hauptprogramms sind für das UP sichtbar.
– Aufgerufene UPe können vom HP (aufrufendes
Programm) Parameter erhalten.
- Beispiel: int square ( int zhl ) {
int quadrat= zhl*zhl;
return quadrat;
} Funktionsdefinition
Datentyp Bezeichner ( Parameterliste )
Zusammengesetzte Anweisung
12.3 Unterprogramme in C: Funktionen
- Bemerkung: Datentyp (hier: int - Rückgabetyp) entspricht dem Datentyp, den die Funktion zurückgibt (return).
Anweisung ;
Zusammengesetzte Anweisung (compound statement)
{ }
Datentyp Parameterliste
Bezeichner ,
12.3.1 Parameter
– Unterprogramme (Funktionen) sollten für möglichst viele Zwecke einheitlich benutzt werden können.
– Bei vielen Berechnungen ändern sich nur einige Parameter, der Rechengang bleibt gleich.
– An verschiedenen Stellen des Hauptprogramms tragen diese Parameter unterschiedliche Namen bzw. Werte.
– Die Parameter im Hauptprogramm sind aktuelle Parameter, die im Unterprogramm dagegen sind formale Parameter.
– Bei Aufruf des Unterprogramms werden formale Parameter zu
aktuellen (Parameter-Übergabe). Dann wird das Unterprogramm mit diesen Werten ausgeführt.
- Beispiel:
#include <stdio.h>
int square ( int zhl ) {
int quadrat = zhl*zhl;
return quadrat;
}
void main() {
int zhl1 = 10;
int zhl2;
zhl2 = square ( zhl1 );
printf("%d quadriert ergibt %d \n",zhl1, zhl2);
}
Aktuelle(r) Parameter Funktionsaufruf
Rückgabewert Rückgabetyp
Formale(r) Parameter UNTERPROGRAMM
HAUPTPROGRAMM Formale(r) Parameter
Aktuelle(r) Parameter
Lokale Variable
Globale Variable Lokale Variable
Globale Variable
Beispiel:
{
int wert;
...
wert = ...
...
return wert;
}
Formale Parameter
Aktuelle Parameter Rückgabetyp
Aufruf der Funktion im Hauptprogramm mit int zahl = funkt (3, 6.9, 'b');
Definition im Unterprogramm:
int funkt (int zahl1, float zahl2, char zeichen )
UP: int func (int zahl1, float zahl2, char zeichen){...}
...
HP: func(3, 6.9, 'b'); /* Aufruf */
Bemerkungen
- Die Datentypen der formalen Parameter und des Rückgabetyps können
einfache Datentypen (int, char, ...), aber auch
Zeiger und strukturierte Datentypen sein.
Beispiele:
void func1 (struct complex cmp_zhl) {...}
void func2 (struct complex *cmp_zhl) {...}
int *func3 (int *pInt) {...}
- Die Reihenfolge der formalen Parameter entspricht der Reihenfolge der aktuellen Parameter.
Beispiel:
- Beispiel:
#include <stdio.h>
void func (int zhl) {zhl = 1;} /* Unterprogramm */
...
void main() /* Hauptprogramm */
{
int zahl = 0;
func(zahl); /* Unterprogrammaufruf */
printf("%d ", zahl);
Werteparameter („Call by value“)
- Die Werte der aktuellen Parameter werden den formalen Parametern zugewiesen.
- Diese verhalten sich wie lokale Variablen.
- Daher: Änderungen im Unterprogramm haben keine Auswirkung auf die Werte der aktuellen Parameter im Hauptprogramm.
/* Ausgabe: 0 */
Variablenparameter („Call by reference“)
Beispiel: (Pascal)
- Parameter-Übergabe: Formale Parameter werden durch die aktuellen Parameter ersetzt, d.h. Adressen werden übergeben.
- Daher: Die Änderungen im Unterprogramm werden an die Umgebung übergeben.
- Aber: Call by reference ist in C nicht explizit vorgesehen, jedoch durch Zeiger simulierbar.
PROGRAM prog (OUTPUT);
VAR zahl: INTEGER;
PROCEDURE func (VAR zhl: INTEGER);
(* Unterprogramm *) BEGIN
zhl:=1 END;
BEGIN (* Hauptprogramm *)
zahl:=0; (* Unterprogrammaufruf *) func (zahl);
WRITE (zahl) END.
(* Ausgabe: 1 *)
Beispiele in C:
#include <stdio.h>
void swap(int zhl1,int zhl2) {
int temp = zhl1;
zhl1 = zhl2;
zhl2 = temp;
}
void main() {
int zahl1=1, zahl2 = 2;
swap(zahl1, zahl2);
printf("Zahl1= %d ", zahl1);
printf("Zahl2= %d", zahl2);
/* Ausgabe: Zahl1= 1 Zahl 2= 2 */
/* Kein Vertauschen! */
#include <stdio.h>
void swap(int *zhl1,int *zhl2) {
int temp = *zhl1;
*zhl1 = *zhl2;
*zhl2 = temp;
}
void main() {
int zahl1=1, zahl2 = 2;
swap(&zahl1, &zahl2);
printf("Zahl1= %d ", zahl1);
printf("Zahl2= %d", zahl2);
/* Ausgabe: Zahl1= 2 Zahl2= 1 */
/* Vertauschen findet statt! */
Call by value: (Simuliertes) Call by reference:
- Bedeutung: Nach Beendigung einer Funktion Rückgabe eines Wertes an das aufrufende Programm.
- Der Rückgabetyp legt den Datentyp dieses Wertes fest.
- Der Befehl return bricht die Ausführung einer Funktion ab und gibt einen Wert an den Aufrufenden zurück.
Beispiel: int square ( int zhl ) {
…
return zhl*zhl;
}
- Falls kein Rückgabewert: void Beispiel: void ausgabe ()
{
printf("Hello World\n");
}
12.3.2 Rückgabetyp/-wert
Sonderfälle
• Das Folgende geht, aber *nicht* nachahmungswürdig:
int square ( int zhl ) {
int square; /* Lokale Variablen mit gleichem Namen wie die Funktion. */
. . .
int quadrat = zhl*zhl;
return quadrat;
}
• Folgendes geht auch, ist aber auch *nicht* nachahmungswürdig : Mit dem return wird ein Pointer auf die Funktion square zurückgegeben.
int square ( int zhl ) {
. . .
return square;
}
(Funktionspointer sind eine „Spezialität„ von C/C++, die wir in dieser Einführungsvorlesung aus gutem Grund unerwähnt lassen.)
• Pointer vor dem Funktionsaufruf erzeugt einen syntaktischen Fehler (es ist auch gut so!):
&square(zhl1);
12.3.3 Felder als Funktionsparameter
- Werden Felder als Parameter in Funktionen benutzt, wird beim
Funktionsaufruf nur die Adresse des ersten Feldelements übergeben.
- Konsequenzen:
Als Argument übergebene Felder können in Funktionen verändert werden.
Keine Feldgrenzenüberprüfung beim Funktionsaufruf mit Feldern!
Bei der Definition von Feld-Parametern: Einen entsprechenden Zeigertyp explizit angeben. Sonst findet automatisch eine
Umwandlung in einen Zeigertyp statt!
Beispiel 1: Die Funktion quad
#include <stdio.h>
void quad(int vector[], int len) {
int idx;
for(idx=0; idx<len; idx++)
vector[idx] = vector[idx]*vector[idx];
}
void main() {
int vektor[10]; int idx;
for(idx=0;idx<10;idx++)
vektor[idx]=idx+1; /* vektor[0]=1, vektor[1]=2, ..., vektor[9]=10 */
quad(vektor, 10);
for(idx=0;idx<10;idx++)
printf ("vektor[%d]=%d\n",idx,vektor[idx]);
quadriert alle Elemente eines Feldes.
Beispiel 1`: Die Funktion quad
#include <stdio.h>
int quad(int zahl) {
return zahl*zahl;
}
void main() {
int vektor[10]; int idx;
for(idx=0;idx<10;idx++) vektor[idx]=idx+1;
for(idx=0;idx<10;idx++)
vektor[idx]=quad(vektor[idx]);
for(idx=0;idx<10;idx++)
printf ("vektor[%d]=%d\n",idx,vektor[idx]);
}
quadriert eine gegebene Zahl.
- Bisher: void main() oder int main() - Ebenfalls möglich:
void main ( int argc, char *argv[] ) oder int main ( int argc, char *argv[] )
int argc: Anzahl der Kommadozeilenparameter (arguments)
char *argv[]: Feld mit Zeigern auf die einzelnen Parameter
Zeigerfeld argv dient zur „Kommunikation“ mit dem
Betriebssystem (das Betriebssystem ruft beim Start des Programms die main-Funktion auf)
Beispiel 2: Die main-Funktion
- Beispiel: Ausgabe der einzelnen Elemente der Kommandozeile.
#include <stdio.h>
void main ( int argc, char *argv[] ) {
int idx;
for(idx=0; idx<argc; idx++) {
printf("Parameter %d : %s \n", idx, argv[idx]);
} }
- Der ausführbare Objekt-Code sei in der Datei dtei . Die Ausgabe ist dann:
Parameter 0 : <Pfad-Name>dtei.exe
Parameter 1 : <Kommandozeilenparameter>
...
12.3.4 Bemerkungen
- Keine Schachtelung von Funktionen in Standard (ANSI)-C!
- ABER: Funktionen dürfen sich selbst aufrufen (Rekursion).
- Zeiger auf Funktionen sind möglich.
12.4 Bibliotheken
- „Wer alles selber macht, ist (auch) selber Schuld!“
- Denn: In Programmen werden öfter Lösungen für Teilaufgaben benötigt, die bei vielen Programmierern immer wieder vorkommen.
- Es ist nicht nötig, für diese Lösungen selber (Quell-)Programme zu schreiben, zu übersetzen usw. Oft stehen sie allen Benutzern in einer effizienten Form (Objekt-Code) zur Verfügung.
- Die meisten höheren Programmiersprachen bieten daher diese Lösungen im Sprachumfang als Bibliotheksfunktionen an.
<assert.h> <ctype.h> <errno.h> <float.h> <limits.h>
<locale.h> <math.h> <setjmp.h> <signal.h> <stdarg.h>
<stddef.h> <stdio.h> <stdlib.h> <string.h> <time.h>
- Die Standard Library ist eine standartisierte Bibliothek für C - Stellt wichtige Funktionen bereit zur
- Ein- und Ausgabe
- Zeichenketten- (String-)Verarbeitung - Mathematische Funktionen
- Speicherverwaltung usw.
Standard Library für C
Header-Dateien der Standard-Library: