Menu Close

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
}

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.

Index