Static-Kennzeichnung
- Wenn eine Klasse ein static-Memberattribut enthält, wird für alle Objekte der gleiche Speicher verwendet ( alle Objekte der Klasse haben Zugriff auf das eindeutige Attribut und dessen Werte )
- Beispiel zu static:
class mitzaehl {
public:
mitzaehl(){++zahl;}
~mitzaehl(){--zahl;}
int anz(){return zahl;}
private:
static int zahl;
};
int mitzaehl::zahl = 0;
int main() {
mitzaehl a, b;
cout << a.anz() << ' ' << b.anz();
return 0;
}
Templates
- Templates erlauben ein effizienteres Arbeiten
- Sie ermöglichen das Wiederverwenden von „Low-Level“-Operationen
Der void-Pointer
- void ist kein Daten-Typ!
- Es gibt keine Objekte vom Typ void!
- Mittels void* lassen sich Zeiger auf Speicherbereiche erstellen, die vom Compiler nicht interpretiert werden
- Dadurch lassen sich beliebige Zuweisungen erstellen, außer:
- keine Dereferenzierung für void* möglich
- keine Indexoperation für void* möglich
- keine Inkrementierung für void* möglich
- → void* ist nur zum Kopieren da
- Umwandlung mittels void*
- Umwandlungen machen Sinn, aber der Compiler hinterfragt diese Aktionen nicht mehr (Vorsicht geboten!)
- dynamic_cast ← Erst zur Laufzeit
- static_cast ← Bereits beim Kompilieren
// Zuweisung
int* pi = new int;
void* pro = pi
// Explizite Umwandlung geht
int* pi = static_cast<int*>(pv);
Wiederholung: Datenfelder / Arrays
- Dynamische Datenfelder können nur mittels „new“ und „delete“ realisiert werden, ohne befinden sich die Datenfelder im Stack.
- Der reservierbare Speicher im Stack ist kleiner als der Heap.
- Die Adresse eines Arrays ist gleichzeitig die Adresse des ersten Elements vom selben Array.
- Arrays kennen ihre Größe nicht, somit kann durch eine falsche Index-Zuweisung in den Speicher „wüst“ geschrieben werden. Es entstehen hierbei Speicherlecks. Deswegen sollte stets die Größe eines Arrays bei Funktionen mitgegeben werden.
- Arrays erzeugen homogene lückenlose Folgen: int – int – int
- nur hierdurch lässt sich der Indexoperator anwenden
- Operationen:
- Gleichheit der Operationen: int pi[ ] = int* pi;
- Initialisierer Syntax: int a[ ] = { 1,2,3,4 };
Datenfelder und Zeiger
void f( int pi[] ) { // gleichbedeutend mit void f( int* pi )
int a[] = { 1,2,3,4 };
//int b[] = a;// FEHLER: Arrays kennen keine Kopieroperation
pi = a; // OK, ist aber keine Kopie: pi zeigt jetzt
// auf das erste Element von a
int* p = a; // p zeigt auf das erste Element von a
int* q = pi; // q zeigt auf das erste Element von a
}
// Am Ende ist die Adresse von pi verloren gegangen.
Klassen und Struct
- Wenn bei einer Klasse keine Zugriffsart angegeben ist, so geht der Compiler automatisch von private aus.
- Bei Struct ist stehts alles Public, da es keine Kapselung besitzt.
- Ein Copy-Konstruktor kopiert das gesamte Objekt, diese Funktion gibt es nur bei Klassen.
Der Adressoperator „&“
int a( 0 ); char ac[20];
void f( int n ) {
int b( 0 );
int* p = &b; // p zeigt auf die lokale Variable namens b
p = &a; // p zeigt auf die globale Variable namens a
char* pc = ac; // Array-Namen sind Zeiger auf ihr erstes Element
pc = &ac[0]; // gleichbedeutend mit pc = ac
pc = &ac[n]; // zeigt auf das n-te Element, es erfolgt
// keine Überprüfung der Zugriffsgrenzen
//..
}
- Datenfeld-Namen werden „beim kleinsten Anlass“ implizit in zeiger umgewandelt
- C++ verwendet standardmäßig „call by Value“
- In Bezug au Datenfelder wird jedoch „call by Reference“ benutzt
Referenzen und Zeiger
int a = 10;
int* z = &a; // der Adressoperator & gibt uns einen Zeiger auf a
*z = 7; // Zuweisung an a durch z
// Der Dereferenzierungsoperator * (oder []) gibt
// uns Zugriff auf das, worauf der Zeiger verweist
int x1 = *z; // lesender Zugriff auf a durch z
int& r = a; // r ist ein Synonym für a
r = 9; // Zuweisung an a durch r
int x2 = r; // lesender Zugriff auf a durch r
z = &x1; // ein Zeiger kann seinen Wert (eine Speicheradresse)
// ändern, d.h. auf ein anderes Objekt verweisen
r = &x1; // FEHLER: eine Referenz kann ihren Wert nicht ändern
- Unterschied Zeiger und Referenz: Eine Referenz ist fest auf ein Objekt bzw. Variable zugewiesen. Die Adresse eines Zeigers kann beliebig geändert und neu zugewiesen werden.
- „Hängender Zeiger“ → Zeigt auf ein nicht existierendes Objekt. Dies kann bei Der Rückgabe von Adressen aus Unterfunktionen und temporären Objekten schnell passieren.
- Tipp 1: Keine Zeiger verwenden, wenn es nicht anders geht.
- Tipp 2: New/Delete nur in Konstruktoren/Destruktoren verwenden
MyVector (Ergebnis)
- Space steht an der letzten Stelle des MyVector.
- Size beginnt an der erstellen Stelle und wandert mit jedem push_back inkrementell weiter.
- Sobald size an space angrenzt ist der MyVector voll und es wird ein neues Array, mit der doppelten Größe, reserviert.
- Im Anschluss werden die Werte übertragen und das alte Array freigegeben.
class myVector {
int sz; // size
int space; // size + weiterer Platz
double* elem; // Zeiger auf die Elemente
public:
myVector( ); // Standardkonstruktor
explicit myVector( int ); // ein Konstruktor
myVector( const myVector& ); // Copy-Konstruktor
myVector& operator=( const myVector& ); // Zuweisungsoperator
~myVector( ); // Destruktor
double& operator[]( int ); // Indexoperator
int size( ) const; // Anzahl Elemente
double get( int ) const; // read
void set( int, double ); // write
void reserve( int );
void resize( int );
void push_back( double );
int capacity( ) const { return space; }
};
void myVector::reserve( int newspace ) {
if( newspace <= space ) return; // nie weniger Platz holen
double* p = new double[newspace]; // mit new Speicher allokieren
for( int i=0; i<sz; ++i ) p[i]=elem[i]; // Elemente kopieren
delete[] elem; // alten Speicher freigeben
elem = p;
space = newspace;
}
void myVector::resize( int newsize ) {
reserve( newsize ); // Speicher im Heap reservieren
for( int i = sz; i<newsize; ++i ) // Initialisierung der
elem[i] = double(); // zusätzlichen Elemente
sz = newsize;
}
void myVector::push_back( double d ) {
if( space==0 ) reserve( 8 ); // Platz aus dem Heap holen…
else
if( sz==space )
reserve( space*2 ); // Kein Platz mehr? Aus dem Heap holen…
elem[sz] = d; // d anhängen
++sz; // und den Elementezähler erhöhen
}