Unterprogramme, Sichtbarkeit, Referenzen und

Werbung
Unterprogramme, Sichtbarkeit, Referenzen und Zeiger Unterprogramme: Unterprogramme sind abgekapselte Programmfragmente, welche es erlauben, bestimmte Aufgaben in wiederverwendbarer Art umzusetzen. Funktionsdefinition: [typ] name ( [deklarationsliste] ) { // Anweisungen return [ausdruck]; // Rückgabewert } Eine grundsätzliche Unterscheidung zwischen Funktionen und Prozeduren existiert in C++ nicht. „Prozeduren“ unterscheiden sich von Funktionen dadurch, dass sie keinen Rückgabewert besitzen. Sie sind vom Typ void, werden meist aber ebenfalls als Funktionen bezeichnet. Prozedurdefinition: void name ( [deklarationsliste] ) { // Anweisungen } Beispiele: void daten_speichern (char name[20], int gebjahr) { // ... Datenbankspeicherung ... // ... Bestätigung ... cout << "Folgende Daten gespeichert:" << endl << "Name : " << name << endl << "Geburtsjahr: " << gebjahr << endl; } int lese_gebjahr (char name[20]) { // ... Datenbank abfragen ... // ... Ergebnis in Variable "gebjahr" speichern return gebjahr; } int main() { char name[20]; int jahr; cout << "Geben Sie ihren Namen ein: "; cin >> name; cout << "Geben Sie ihr Geburtsjahr ein: "; cin >> jahr; daten_speichern(name,jahr); // Name und Geburtsjahr speichern cout << lese_gebjahr(name) << endl; // Geburtsjahr ausgeben return 0; } Jede Funktion muss vor ihrer Verwendung bekannt sein, d.h. der Name und Typ der Funktion sowie die Typen der Parameter der Deklarationsliste müssen vorher angegeben werden. Um diese Richtlinie leichter einhalten zu können (bzw. um sie überhaupt einhalten zu können, wenn sich z.B. zwei Funktionen gegenseitig aufrufen), gibt es die Möglichkeit Unterprogramme vor der eigentlichen Definition zu deklarieren. Dabei muss nur die Schnittstelle der Funktion, d.h. ihr Typ, ihr Name und die Parametertypen, angegeben werden. Prototypen für obige Beispiele: void daten_speichern (char name[20], int gebjahr); int lese_gebjahr (char name[20]); oder void daten_speichern (char[20], int); int lese_gebjahr (char[20]); Erweitertes Beispiel: // Prototypen void daten_speichern (char[20], int); int lese_gebjahr (char[20]); // Hauptfunktion int main() { ... return 0; } // Definitionen void daten_speichern (char name[20], int gebjahr) { ... } int lese_gebjahr (char name[20]) { ... } Übergabe von Funktionsparametern: Call‐by‐Value ...bezeichnet die Übergabe von Werten beim Aufruf einer Funktion. Die aufgerufene Funktion arbeitet dann mit einer Kopie der Argumente, welche (nur) lokal gültig ist. (entspricht Eingabeparametern) #include <iostream> using namespace std; int wired(int, int); int main() { int a=4, b=5, c; c = wired(a,b); cout << a << " " << b << " " << c << endl; return 0; } int wired(int a, int b) { a = a * 3; b = b * 4; return (a+b); } Frage: Wie sieht die Ausgabe aus? Call By Reference ...bezeichnet die Übergabe von Argumentadressen (Aliasnamen) beim Aufruf einer Funktion. Die aufgerufene Funktion arbeitet dann direkt mit den Argument‐ Objekten und kann diese auch verändern. (entspricht Ein‐/ Ausgabeparametern) #include <iostream> using namespace std; int wired(int&, int&); int main() { int a=4, b=5, c; c = wired(a,b); cout << a << " " << b << " " << c << endl; return 0; } int wired(int &a, int &b) { a = a * 3; b = b * 4; return (a+b); } Frage: Wie sieht die Ausgabe aus? Gegenüberstellung der Verfahren: Verfahren Vorteile Call‐by‐Value z
z
Call‐by‐
Reference z
z
Nachteile Argumente können beliebige Ausdrücke sein Funktion kann keine unbeabsichtigten Änderungen der Argumente vornehmen z
Argumente werden nicht kopiert, weshalb Übergabe großer Parameter schnell ist mehrere Werte können über Referenzparameter zurückgegeben werden z
z
durch Kopie‐Charakter ist Übergabe großer Objekte rechen‐ und speicheraufwändig nur ein Ergebnis über return zurückgebbar Argumente müssen Objekte im Speicher mit korrektem Typ sein ‐> beliebige Ausdrücke in der Übergabe (z.B. a+b) nicht möglich Zeiger: Bisher wurden Variablen immer über die Angabe ihres Typs und eines Namens deklariert. Der Name einer Variablen stellt im Prinzip lediglich eine für uns gebräuchlichere Bezeichnung für eine Speicheradresse dar. Der Compiler ordnet diesem Namen beim Übersetzen automatisch einen freien Speicherplatz zu. Wie groß der zugeordnete Speicher ist, wird durch den Typ der Variablen bestimmt. Ein integer Wert benötigt z.B. 4 Byte. Beispiel: Anlegen einer Variablen In C++ gibt es jedoch auch die Möglichkeit direkt mit Speicheradressen zu arbeiten. Dies geschieht über Zeiger (engl. Pointer), die „auf eine Speicheradresse zeigen“. Der Wert einer Zeigervariablen ist damit eine Speicheradresse, ihr Typ gibt an „auf was“ sie zeigt. Um eine Zeigervariable (einen Zeiger) zu deklarieren verwendet man den Ausdruck typ *variablenname. int *a; // Zeigervariable mit dem Namen a die auf einen Integerwert zeigt Zeiger sind eine neue Art von Typen! Frage: Wohin zeigt a nach obiger Anweisung! Beispiel: Anlegen einer Zeigervariablen Beispiel: Referenzierung & int a = 5; int *ptr; ptr = &a; // ptr enthält nach dieser Anweisung die Adresse von a int b = 7; int *ptr_2 = &b; ptr_2 = ptr; Frage: Zeiger auf Zeiger? Der Operator & liefert eine Referenz, d.h. die Speicheradresse, eines Objektes. Beispiel: Dereferenzierung * cout << *ptr; Der Operator * betrachtet den Wert, der in der Variablen gespeichert ist als Speicheradresse und liefert den Wert zurück, der sich an dieser Adresse im Speicher befindet. Er folgt sozusagen dem Zeiger zu dessen „Ziel“. Zeiger und Referenzen setzen die bisher kennengelernten C++ Regeln nicht außer Kraft. Nach wie vor muss jeder Speicherplatz zunächst angefordert werden! Zeigervariablen können jedoch beliebige Werte enthalten. Wird auf Speicheradressen zugegriffen, die nicht zum Programm „gehören“, führt dies, aus Sicherheitsgründen, zu einem Programmabsturz. Arithmetische Operationen mit Zeigern sind daher – zwar möglich – sollten aber im Allgemeinen unterlassen werden. Beispiel: Speicher anfordern: Variablen <–> Zeiger, Addition Dynamisches Anfordern von Speicher: Nach allem bisher gesagten wären Zeiger höchst überflüssig und die Möglichkeiten, die Referenzen bereitstellen, würden vollkommen ausreichen. Variablen: Name für festen Speicherplatz bekannter Größe Referenzen: feste Adresse existierender(!) Objekte (die eine bekannte Größe haben) Zeiger: Adresse eines beliebigen Speicherbereich, der als ein bestimmter Typ interpretiert wird Beispiel Mit Zeigern wird es möglich Speicher erst zur Laufzeit dynamisch anzufordern! Beispiel: Zeiger und Arrays, Indexadressierung, Zeigeradressierung #include <iostream> using namespace std; int main() { int a; int *ptr; int *arr; cout << "Zahl eingeben: "; cin >> a; ptr = &a; cout << "Adresse von ptr: " << ptr << endl; cout << "Wert an Adresse: " << *ptr << endl << endl; ptr = new int; *ptr = 7; cout << "Adresse von ptr: " << ptr << endl; cout << "Wert an Adresse: " << *ptr << endl << endl; arr = new int[a]; arr[0] = 5; *(arr + 1) = 6; cout << "Erstes Array‐Element: " << arr[0] << endl; cout << "Zweites Array‐Element: " << arr[1]; return EXIT_SUCCESS; } Der Programmierer ist für selbst angeforderten Speicherplatz auch selbst verantwortlich, da anders als bei Variablen keine feste Bindung zwischen Name und Speicheradresse besteht. Dynamisch angeforderter Speicher muss demnach auch manuell freigegeben werden. Beispiel (Unterprogramm!!!) Übersicht Zeiger – Referenzen Es seien zwei Variablen „x“ und „y“ vom Typ „double“ mit dem Wert „2.50“ gegeben. Referenzen Zeigervariablen Definition double &ref1 = x; double *ptr1 = &x; double &ref2 = y; doube *ptr2 = &y; Dereferenzierung ref1 *ptr1 ref2 *ptr2 Verweisziel ändern nicht möglich ptr1 = &y; ptr2 = &x; Verweis‐Wert ändern ref1 = 5.345; *ptr1 = 7.893; Leerer Verweis nicht möglich ptr2 = NULL; Skizze der Speicherzellen Adresse Name Inhalt Adresse Name 456FD8 x, ref1 5.345 456FD8 x 456FD4 y, ref2 2.50 456FD4 y 456FD0 ptr1 456FCC ptr2 ref1, ref2 ... Referenzen (Aliasname) ptr1, ptr2 ... Zeigervariablen &x ... Speicher‐Adresswert der Variable x NULL ... leerer Adresswert (intern gleich 0) Zeiger als Parameter: int wired(int *a, int *b) { *a = *a * 3; *b = *b * 4; return (*a+*b); } Aufruf: int main() { int a = 3; int b = 3; int c = wired(&a, &b); } Zeiger soll im Unterprogramm verändert werden (Zeigerreferenz): void insert_first(element *&liste); Inhalt 2.50 7.893 456FD4 0 
Herunterladen