1 ” Versteckte“ Zeiger 2 Zeiger und das Schlüsselwort const

Werbung
1
Versteckte“ Zeiger
”
Felder
Ein Feld ist ein zusammenhängender Speicherbereich, in dem hintereinander Variablen des entsprechenden
Typs – die Komponenten oder Elemente des Feldes – liegen. Der Name eines Feldes ist immer ein Zeiger
auf das erste Element des Feldes:
double Feld[10];
double *PDbl;
PDbl=Feld;
Dies wird besonders bei der Übergabe eines Feldes an eine Funktion deutlich:
void MachWas ( double Feld[] );
Eigentlich wird nur ein Zeiger auf das erste Element des Feldes Feld übergegeben. Änderungen von Feld
haben auch außerhalb der Funktion MachWas Bestand! Um zu verhindern, dass das Feld geändert wird,
kann man die Funktion als
void MachWas ( const double Feld[] );
deklarieren. Weil der Name eines Feldes nur ein Zeiger ist, können Funktionen auch keine Felder zurückgeben:
typedef int IntFeld[5];
IntFeld GehtNicht () // Fehler
{
int IFeld[5];
return IFeld;
}
Als lokale Variable wäre das Feld IFeld nach Beendigung der Funktion schon zerstört. Es würde daher
nur ein Zeiger auf einen Speicherplatz in undefiniertem Zustand zurückgegeben.
Referenzen
Eine Referenz Typ &Name (z. B. int &Zahl) wie im Funktionskopf
int TuWas1 ( int &Zahl ) { Zahl=3; return Zahl; };
ist nichts anderes als ein Zeiger:
int TuWas2 ( int *Zahl ) { *Zahl=3; return *Zahl; };
Ein nicht veränderbarer Zeiger, um genau zu sein:
int TuWas2 ( int *const Zahl ) { *Zahl=3; return *Zahl; };
Der einzige Unterschied ist der Zugriff auf die Speicherzelle, auf die der Zeiger zeigt: Im ersten Fall wird
automatisch dereferenziert (beispielsweise Zahl=3), im zweiten Fall nicht (man muss also stattdessen
*Zahl=3 schreiben).
2
Zeiger und das Schlüsselwort const
Da ein Zeiger einerseits selbst eine Speicherstelle belegt und andererseits auf eine Speicherstelle zeigt,
gibt es zwei verschiedene Stellen, auf die sich const beziehen kann: Ein Zeiger
const int *Zahl
ist ein Zeiger auf ein const int-Objekt. Das bedeutet, dass man zwar den Zeiger selbst ändern kann,
aber nicht den Inhalt der Speicherstelle, auf die er zeigt. Genau umgekehrt verhält es sich bei
int *const Zahl
Man kann den Zeiger nicht ändern, aber den Inhalt der Speicherstelle, auf die er zeigt. Jegliche Änderung
ist bei der Kombination
const int *const Zahl
untersagt. Bei den Funktionen des vorigen Abschnitts kann man durch Deklaration des Zeigers bzw. der
Referenz als const (const int &Zahl bzw. const int *const Zahl) eine Änderung von Zahl verhindern — der Compiler meldet dann bei der Zuweisung Zahl=3 bzw. *Zahl=3 einen Fehler.
1
Klassen
Motivation: Wir möchten einen neuen Datentyp Complex für komplexe Zahlen haben. Damit soll es
möglich sein, z.B. folgenden Programmcode zu schreiben:
#include "complex.h"
void main()
{
Complex x,
y= 3.2,
z( 1, -1);
// 0.0 + 0.0 i
// 3.2 + 0.0 i
// 1.0 - 1.0 i
x= (y+z) * z.ConjComplex();
Complex c= 5*x;
cout << c << endl;
}
Der Schlüssel dazu ist, eine neue Klasse Complex zu schreiben. Diese Klasse enthält:
• die reinen Daten (z.B. Real-/Imaginärteil), die sogenannten Elemente oder Member einer Klasse,
und zusätzlich
• Funktionen zur Manipulation dieser Daten, die sogenannten Elementfunktionen oder Memberfunktionen.
In einer Klasse stecken also sowohl die Daten als auch die Funktionalität eines Datentyps. Die Klasse
Complex steht für den Datentyp Complex. Die Variablen x, y, z, c des Typs Complex heißen Objekte
dieser Klasse.
In der Regel sind die Daten einer Klasse vor Manipulation von außen geschützt, d.h. sie stehen im
private-Bereich einer Klasse. Der Benutzer kann auf sie nur über Elementfunktionen zugreifen, welche im
public-Bereich stehen. Dazu müssen entsprechende public-Elementfunktionen bereitgestellt werden, z.B.
für Schreib- und für Lesezugriff.
class Complex
{
private:
double _Re, _Im;
// Real- und Imaginaerteil
public:
...
double GetReal() const { return _Re; }
double GetImag() const { return _Im; }
void SetReal( double Re) { _Re= Re; }
void SetImag( double Re) { _Re= Re; }
...
// Lesezugriff
// Schreibzugriff
};
Der Benutzer kann nur auf den public-Bereich einer Klasse direkt zugreifen. Die public-Elementfunktionen
stellen somit die Schnittstelle der Klasse dar. Auf die Elemente im private-Bereich einer Klasse dürfen
nur die Elementfunktionen dieser Klasse zugreifen (und friends dieser Klasse, dazu aber später). Im
Folgenden sind einige Beispiele für richtigen und falschen Zugriff auf die Datenelemente aufgeführt:
Complex c( 1.1, 2.2);
// 1.1 + 2.2i
// Schreibzugriff
c._Im= 0.7;
c.SetImag( 0.7);
// Fehler! _Im ist private!
// ok
double x;
// Lesezugriff
x= c._Re;
x= c.GetReal();
// Fehler! _Re ist private!
// ok
2
Überladen von Operatoren
Um Ausdrücke wie x=y+z; schreiben zu können, muss der Operator + und der Zuweisungsoperator =
für Objekte vom Typ Complex definiert werden. Eine Addition zweier komplexer Zahlen entspricht intern dem Aufruf einer Funktion mit dem Namen operator+, die zwei Complex-Objekte als Parameter
entgegennimmt und ein Complex-Objekt zurückgibt:
Complex operator+ (const Complex&, const Complex&);
y+z entspricht dann dem Aufruf von operator+( y, z). Man könnte operator+ auch als Elementfunktion realisieren, dann würde y+z gerade y.operator+(z) entsprechen. Der Zuweisungsoperator ist z.B.
in Complex als Elementfunktion realisiert.
Konstruktoren, Destruktor
Eine wichtige Sache fehlt bisher: Wie werden Objekte neu erzeugt, und wie werden sie wieder gelöscht?
Dafür gibt es Konstruktoren bzw. den Destruktor. Sie haben den gleichen Namen wie die Klasse (Destruktor zusätzlich mit einem ~ davor). Sie werden nie explizit aufgerufen, sondern nur automatisch:
• Konstruktor: beim Anlegen neuer Objekte, z.B. bei der Variablendeklaration.
• Destruktor: beim Löschen von Objekten, z.B. wenn ein Objekt seinen Gültigkeitsbereich verlässt.
Zu Konstruktoren und Destruktor gibt es noch viel mehr zu sagen, aber erst in der nächsten Stunde...
Die Klasse Complex und einige Operatoren dazu sind im Folgenden aufgelistet:
class Complex
{
private:
double _Re, _Im;
// Real- und Imaginaerteil
public:
Complex()
: _Re( 0), _Im( 0) {}
Complex( double Re)
: _Re( Re), _Im( 0) {}
Complex( double Re, double Im)
: _Re( Re), _Im( Im) {}
Complex( const Complex& c)
: _Re( c._Re), _Im( c._Im) {}
~Complex() {}
// Elementfunktionen:
double GetReal() const
double GetImag() const
double GetRad () const
double GetPhi () const
{
{
{
{
return
return
return
return
// Konstruktor 1
// Konstruktor 2
// Konstruktor 3
// Kopierkonstruktor
// Destruktor
_Re; }
_Im; }
sqrt( _Re*_Re + _Im*_Im); }
atan( _Im/_Re); }
void SetCartesian( double Re, double Im);
void SetPolar
( double r, double phi);
Complex ConjComplex() const;
// Angabe in kart. Koord.
// Angabe in Polarkoord.
// konjugiert-komplex
Complex& operator= ( const Complex& rechteSeite);
};
// Operatoren + Complex operator+
Complex operatorComplex operator*
Complex operator/
*
(
(
(
(
/
const
const
const
const
Complex&,
Complex&,
Complex&,
Complex&,
const
const
const
const
Complex&);
Complex&);
Complex&);
Complex&);
// Ausgabeoperator
ostream& operator<< (ostream&, const Complex&);
3
// Zuweisungsoperator
Herunterladen