Programmieren, Algorithmen und Datenstrukturen 2 (Vorlesung 9)

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
}
Maximilian
Maximilian

Herzlich willkommen! Ich bin Max, ein Informatiker mit über 15 Jahren Berufserfahrung. Hier teile ich meine Leidenschaften, Erlebnisse und Perspektiven. Ich lade dich ein, gemeinsam mit mir auf eine Entdeckungsreise zu gehen.

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahren Sie, wie Ihre Kommentardaten verarbeitet werden.