~~SLIDYSHOW~~
Strukturen sind vereinfachte Klassen und fassen somit Eigenschaften (Variablen) zusammen.
einfache Def. eines neuen Typs:
struct CDatum { int Tag; int Monat; int Jahr; }; // Das Semikolon beendet die Definition CDatum variable;
Typdef. und Erstellung einer Variable
struct CDatum { int Tag; int Monat; int Jahr; } d; // d ist eine Variable des Datentyps CDatum. CDatum variable;
struct { // Dieser Datentyp hat keinen eigenen Namen. int Tag; int Monat; int Jahr; } d; // Der Datentyp von d ist anonym
struct Kontobewegung { int KontoNr; char NameInhaber[20]; struct { int Tag; int Monat; int Jahr; } Datum; char BewArt; double Betrag; };
Zugriff ist per '.' Punkt möglich.
test.variable;
Kontobewegung k; // kann man die Elemente der Variablen k folgendermaßen ansprechen: k.KontoNr // ein Ausdruck des Datentyps int k.Datum // ein Ausdruck des Datentyps CDatum // Die drei Elemente von K.Datum lassen sich einzeln ansprechen durch k.Datum.Tag // ein Ausdruck des Datentyps int k.Datum.Monat k.Datum.Jahr
Definitionen sind auch mit geschwungenen Klammern möglich:
struct CDatum { int Tag; int Monat; int Jahr; } d; CDatum test = {1, 2, 3}; cout << test.Monat; // 2
Ein Zeiger speichert eine Speicheradresse und verweist so auf einen bestimmten Platz im Speicher. Mit der Dereferenzierung wird auf die unter der gespeicherten Adresse gespeicherten Daten zugegriffen.
int *p;
Erstellt einen Zeiger auf eine Ganzzahl.
Mit der Dereferenzierung kann auf den Wert der referenzierten Variable zugreifen.
int *p; *p = 17;
Mit dem → Operator
struct T { int a, b; } T *var; (*var).a = 1; var->b = 2;
Gibt die Adresse einer regulären Variable zurück.
int i=17; double d=18; char c='A'; &i // Zeiger auf int &d // Zeiger auf double &c // Zeiger auf char
int* pi; double* pd; pi = &i; // pi wird die Adresse von i zugewiesen pd = &d; // pd wird die Adresse von d zugewiesen char* pc=&c;// initialisiere pc mit &c
Der Datentyp void* wird als generischer Zeigertyp bezeichnet. Einem generischen Zeiger kann ein Zeiger auf einen beliebigen Zeigertyp zugewiesen werden.
Ein generischer Zeiger zeigt aber auf keinen bestimmten Datentyp. Deshalb ist es nicht möglich, einen generischen Zeiger ohne explizite Typkonversion zu dereferenzieren. Mit einer expliziten Typkonversion kann er aber in einen beliebigen Zeigertyp konvertiert werden.
int* pi; void* pv; // ist die erste und die dritte der folgenden Zuweisungen möglich: pv = pi; pi = pv; // Fehler: Konvertierung nicht möglich pi = (int*)pv; // explizite Typkonversion // Die Dereferenzierung eines generischen Zeigers ist ohne explizite Typkonversion nicht möglich: *pi = *pv; // Fehler: Kein zulässiger Typ int i = *((int*)pv); // das geht
Für einen Datentyp T reserviert „new T“ so viele Bytes auf dem Heap, wie für eine Variable des Datentyps T notwendig sind. Der Ausdruck „new T“ hat den Datentyp „Zeiger auf T“. Sein Wert ist die Adresse des reservierten Speicherbereichs und kann einer Variablen des Typs „Zeiger auf T“ zugewiesen werden:
int* pi; pi=new int; //reserviert sizeof(int) Bytes und weist pi die Adresse dieses Speicherbereichs zu *pi=17; // initialisiert den reservierten Speicherbereich // Am besten initialisiert man eine Zeigervariable immer gleich bei ihrer Definition: int* pi=new int (3445234); // initialisiert pi mit der Adresse *pi=17;
Zur Vermeidung von Speicherlecks ist eine explizite Löschung empfohlen.
delete pi;
Listen sind Strukturen, die einen Wert speichern und auf ihrem Nachfolger verweisen.
struct CWaggon { AnsiString data; // die Nutzdaten CWaggon *next; // Zeiger auf den nächsten Knoten };
Ein solcher Datentyp wird auch als rekursiver Datentyp bezeichnet.
Im Programm muss nur ein Zeiger gespeichert werden, der auf den ersten Knoten der Liste verweist:
CWaggon *first; // Zeiger auf den ersten Knoten
Den letzten Knoten der Liste kennzeichnet man durch einen Zeiger next mit dem Wert 0. Grafisch wird dieser Wert oft durch einen schwarzen Punkt dargestellt. Dann hat die verkettete Liste einen eindeutigen Anfang und ein eindeutiges Ende:
Als nächstes sollen Anweisungen gesucht werden, mit denen man vor einem Knoten, auf den ein Zeiger n0 zeigt,
einen neuen Knoten einfügen kann, auf den n0 dann zeigt:
Das ist mit den Anweisungen unter 1. und 2. möglich:
1. Ein Zeiger tmp soll auf einen neuen Knoten zeigen, der mit new erzeugt wird
CWaggon *tmp = new CWaggon; // 1.1
und dem die Daten durch eine Anweisung wie
tmp->data = d2; // 1.2
zugewiesen werden. Dadurch ergibt sich:
2. Der neue Knoten *tmp wird dann mit den beiden Anweisungen
tmp->next = n0; // 2.1 n0 = tmp; // 2.2
in die Liste eingehängt:
Diese Anweisungen werden mit der Funktion
CWaggon *newWaggon(T &data, CWaggon *next) // gibt Zeiger auf neuen Listenknoten{d0,nx0} zurück, { // wobei d0 und nx0 die Argumente für data und next sind. CWaggon *n = new CWaggon; // 1.1 n->data = data; // 1.2 n->next = next; // 2.2 return n; // Nachbedingung: Der Rückgabewert zeigt auf } // einen neuen Knoten {d0,nx0}
durch den Aufruf
n0 = newWaggon(d2,n0); // 2.1
ausgeführt. n0 zeigt danach auf einen neu erzeugten Knoten, dessen Element next auf den Knoten zeigt, auf den das Argument für next zeigt. Falls das Argument für next den Wert 0 hat, zeigt der Funktionswert auf einen Knoten mit next==0.
Das Ergebnis der ersten drei Ausführungen der Anweisung
first = newWaggon(di,first);
mit den Daten d1, d2 usw., wobei der Wert von first zuerst 0 sein soll, ist in dem folgenden Ablaufprotokoll dargestellt. Dabei sind die Zeiger auf die von newWaggon erzeugten Knoten mit n1, n2 usw. bezeichnet, und ein Knoten, auf den ein solcher Zeiger zeigt, mit →{di,nj}. Der Ausdruck →{d2,n1} in der Spalte n2 ist also nichts anderes als eine Kurzschreibweise für
Die Werte in den Spalten n1, n2 usw. erhält man einfach durch Einsetzen der Argumente in die Nachbedingung von newWaggon:
// first n1 n2 n3 first = 0; 0 first = newWaggon(d1,first); n1 ->{d1,0} first = newWaggon(d2,first); n2 ->{d2,n1} first = newWaggon(d3,first); n3 ->{d3,n2}
Dieses Ablaufprotokoll illustriert, wie der erste dieser Aufrufe einen ersten Knoten mit next==0 erzeugt, auf den first dann zeigt, und wie jeder weitere Aufruf einen neuen Knoten am Anfang in die verkettete Liste einhängt, auf die first zeigt.
Die Funktionsweise von newWaggon beruht insbesondere darauf, dass eine mit new erzeugte Variable bis zum Aufruf von delete existiert. Im Unterschied zu einer gewöhnlichen Variablen wird ihr Speicherplatz nicht mit dem Verlassen des Blocks wieder freigegeben, in dem sie erzeugt wurde.
– Deshalb existiert die Variable *tmp, die mit CWaggon *tmp=new CWaggon; erzeugt wurde, auch noch nach dem Verlassen der Funktion newWaggon.
– Der Zeiger tmp ist dagegen eine „gewöhnliche“ lokale Variable, deren Speicherplatz mit dem Verlassen des Blocks wieder freigegeben wird. Da der Wert von tmp dem Element next des Funktionswerts zugewiesen wird, kann man die lokal erzeugte Variable *tmp über den Funktionswert auch außerhalb der Funktion verwenden, in der sie erzeugt wurde.
Um die Liste zu Durchlaufen, kann man sich mit einem Hilfszeiger durch die Liste bis zu Ende durchhangeln:
CWaggon *tmp=start; while (tmp != 0) { Form1->Memo1->Lines->Add(tmp->data); tmp = tmp->next; }
Dabei wird nicht nur auf das folgende, sonder auch auf das vorherige Element verwiesen.
struct CWaggon { AnsiString data; CWaggon *next; CWaggon *previous; }; CWaggon *first=0, *last=0;
CWaggon *tmp = new CWaggon; tmp->data = Edit1->Text; tmp->next = first; tmp->previous = 0; if (tmp->next!=0) tmp->next->previous = tmp; else last = tmp; first = tmp; Edit1->Text = "";
CWaggon *tmp = new CWaggon; tmp->data = Edit2->Text; tmp->previous = last; tmp->next = 0; if (tmp->previous!=0) tmp->previous->next = tmp; else first = tmp; last = tmp; Edit2->Text = "";
Baumstrukturen werden aus Knoten aufgebaut, die einen Zeiger auf einen linken und rechten Teilbaum enthalten:
Ein Baumknoten kann durch den Datentyp CKnoten dargestellt werden:
struct CKnoten { int data; // die Nutzdaten sind in diesem Beispiel nur Zahlen CKnoten* left; CKnoten* right; };
Der Zeiger auf den obersten Knoten des Baums wird oft als root bezeichnet:
CKnoten *root=0;
void einhaengen(CKnoten *&parent, CKnoten *neu) { if (parent==0) { parent=neu; } else { if (neu->data<=parent->data) { einhaengen(parent->left,neu); } else { einhaengen(parent->right,neu); } } } void __fastcall TForm3::Button1Click(TObject *Sender) { CKnoten *neu = new CKnoten; if (Edit1->Text != "") { neu->data = StrToInt(Edit1->Text); neu->left = 0; neu->right = 0; Edit1->Text = ""; einhaengen(root,neu); } Edit1->Focused(); }
Knoten h wird gelöscht. Es werden 3 Fälle unterschieden:
In den ersten beiden Fällen wird einfach der Teilbaum nach oben gerückt, im 3 Fall muss der Wert der Wurzel verglichen werden.
if (h!=0) { if (StrToInt(Form1->Edit3->Text) <= h->data) loeschen(h->left); else if (StrToInt(Form1->Edit3->Text) > h->data) loeschen(h->right); if (StrToInt(Form1->Edit3->Text) == h->data) { if (h->left == 0 && h->right == 0) { //Blatt delete(h); h = 0; } else if (h->left != 0 && h->right == 0) { //Halbblatt links // CKnoten *h1 = h; // h wird h->left; delete(h); //delete(h1) if not works h = h->left; //to delete if not works } else if (h->left == 0 && h->right != 0) { //Halbblatt rechts // CKnoten *h1 = h; // h wird h->right; delete(h); //delete(h1) if not works h = h->right; //to delete if not works } else if (h->left != 0 && h->right != 0) { //2 Kinder CKnoten *h1 = h; //Hilfsknoten CKnoten *h2 = h->left; h = h->right; //größerer Teil rückt nach oben delete(h1); h1 = h; while (h1->left != 0) h1 = h1->left; //weiterwandern, immer links bis 0 h1->left = h2; } } }
Klassen funktionieren ähnlich wie Strukturen, bieten aber mehr Möglichkeiten. Sie bestehen aus 2 Arten von Elementen:
Funktionen können innerhalb als auch außerhalb deklariert werden:
class Punkt { double x, y; void Init(double x_, double y_) { x = x_; y = y_; } AnsiString toStr() { return "(" + FloatToStr(x) + "|" + FloatToStr(y) + ")"; } // z.B. (2,35|3,46) void show() { Form1->Memo1->Lines->Add(toStr()); } };
Eine Deklaration von außen ist mit :: möglich:
void C2DPunkt::Init(double x_, double y_) { x = x_; y = y_; } AnsiString C2DPunkt::toStr() { return "(" + FloatToStr(x) + "|" + FloatToStr(y) + ")"; } // z.B. (2,35|3,46) void C2DPunkt::show() { Form1->Memo1->Lines->Add(toStr()); }
Mit den Zugriffsrechten private, protected und public kann man für jedes Klassenelement explizit festlegen, ob man über ein Objekt darauf zugreifen kann.
Diese Spezifizierer definieren ab ihrer Angabe einen Abschnitt mit Zugriffsrechten, die für alle folgenden Elemente bis zum nächsten solchen Spezifizierer oder bis zum Ende der Klasse gelten. Ein Element aus einem private, protected oder public Abschnitt heißt auch private, protected oder public Element.
Ohne die Angabe eines Zugriffsrechts sind alle Elemente einer mit class definierten Klasse private, während alle Elemente einer mit struct definierten Klasse public sind. Dieses voreingestellte Zugriffsrechte ist der einzige Unterschied zwischen einer mit class und einer mit struct definierten Klasse.
Alle Elemente der Klassen C0 und C1 haben dieselben Zugriffsrechte:
class C0 { int x; int f() { return x; } }; struct C1 { private: int x; int f() { return x; } };
Jeder Zugriff auf diese Elemente über ein Objekt führt zu einer Fehlermeldung:
void test(C0 a, C1 b) { a.x=1;//Fehler: Zugriff auf 'C0::x' nicht möglich a.f();//Fehler: Zugriff auf 'C0::f()' nicht mögl. b.x=1;//Fehler: Zugriff auf 'C1::x' nicht möglich b.f();//Fehler: Zugriff auf 'C1::f()' nicht mögl. }
Mit dem Zugriffsrecht public sind alle diese Zugriffe zulässig:
class C0 { public: int x; int f() { return x; } }; struct C1 { int x; int f() { return x; } };
Eine Klasse kann eine beliebige Anzahl von Abschnitten mit verschiedenen Zugriffsrechten in einer beliebigen Reihenfolge enthalten. Die Reihenfolge der Abschnitte ist dabei ohne Bedeutung.
Die beiden Klassen C1 und C2 sind gleichwertig. Oft werden die verschiedenen Abschnitte wie bei C1 in der Reihenfolge private, protected und public aufgeführt. Wenn man sie aber wie bei C2 in der umgekehrten Reihenfolge anordnet, kommen die Elemente, die für das breiteste Publikum interessant sind, am Anfang:
class C1 { int x; public: int f(C p); }; class C2 { public: int f(C p); private: int x; };
Ein Konstruktor ist eine Methode der Klasse, die denselben Namen wie die Klasse hat. Der Konstruktor hat keinen Rückgabetyp. Eine Klasse kann mehrere Konstruktoren haben, welche sich überladenen, also durch ihre Parameter unterscheiden. Die Parameter können auch Default-Argumente haben.
Ein Konstruktor wird in den folgenden Situationen automatisch aufgerufen:
class C2DPunkt { double x, y; public: C2DPunkt (double x_) { // ein Parameter: x-Wert x = x_; y = 0; } C2DPunkt (double x_, double y_) { x=x_; y=y_; } };
Damit können folgendermaßen Objekte dieser Klasse definiert werden:
C2DPunkt p(3); // p=(3,0) C2DPunkt q(5,6); // q=(5,6)
Dagegen wird diese Definition vom Compiler nicht akzeptiert:
C2DPunkt p; //Fehler: Keine Übereinstimmung für 'C2DPunkt::C2DPunkt()' gefunden
Die beiden Konstruktoren von C2DPunkt können auch durch einen einzigen mit einem Default-Argument ersetzt werden:
class C2DPunkt{ double x,y; public: C2DPunkt(double x_, double y_ = 0) { x = x_; y = y_; } };