C++ ZUSAMMENFASSUNG Christian Forster, 28. August 2007 [email protected] Patrik Rohner, 15. Juli 2008 [email protected] Andreas Forster, 25. August 2011 [email protected] AUFBAU EINES PROGRAMMS #include <iostream> #include <cstdlib> #include <cmath> //für math. func #include <string> //für c++ strings #include "headerfile.h" //einbinden #include <time.h> //Zeitmessung using namespace std; if(f != BLAU) { }; HEXADEZIMALER CODE & ADRESSEN 0,1,…,9,A,B,C,D,E,F (hex) anstelle von 0,1,…,14,15,16 (dec) Adressen werden hexadezimal angegeben. a,a+1,a+2,a+3… int, float (4byte = 32bit) double (8byte = 64bit) 0x22ff70 a[0] 0x22ff70 0x22ff74 a[1] 0x22ff78 0x22ff78 a[2] 0x22ff80 0x22ff7c a[3] 0x22ff88 FLIESSKOMMAZAHLEN Float //structs, functions, enums int main(void) { system(“pause”); return 0; 1bit->sign, 8bit->exponent, 23bit->mantisse Wert = (-1)S x 2(E-127) x (1.F) Bsp1: 0.125 = 2-3 » S -> 0, E -> 124, F -> 0 Bsp3:-1.75=(-1)1*S^(127-127)*(1+2-1+2-2) 0|01111100|00000000000…0 = 0.125 0|01111111|00000000000…0 = 1 1|01111111|11000000000…0 = -1.75 0|00000000|00000000000…0 = 0 0|11111111|00000000000…0 = +infty 0|00000000|10010101110… = NaN } Double 1bit->sign, 11bit->exponent, 42bit->mantisse Wert = (-1)S x 2(E-1023) x (1.F) VARIABELN OPERATOREN int + - / * ^ % x += i 1.1E-5 i++, i-- 32bit . positive und negative ganze Zahlen range: -231 bis 231-1 char int mit 8bit ( = 1 Byte) für Buchstaben char[] Buchstaben Array -> String float Kommazahlen 32bit, Eingabe: 3.0f double Kommazahlen 64bit, Eingabe: 3.0 short .. .. Verkürzung. short int -> 16 bit int long .. .. Verlängerung long int -> 32 bit (wie int) unsigned .. .. nur positive Zahlen int -> 0 bis 232-1 bool Wahrheitswerte (true/1, false/0) VARIABELNNAMEN Keine Leerzeichen, Satzzeichen oder Symbole Keine Zahl oder __ am Anfang case sensitivity – Gross - Kleinschreibung beachten EINFACHE VARIABELN DEKLARIEREN int a,a2; int b = 10; float c = a*b – 0.5; mathematische Operatoren ganzzahliger Rest einer Division 15%6==3 x = x + i; ebenso *=, /=, -= = 1.1*10-5 erhöht / verkleinert i um 1 b=5; c=b++; → c=5, b=6 verwende c=++b für c=6, b=6 Für weitere mathematische Funktionen: #include <cmath> abs(), sqrt(), exp(), log(), cos(), acos() LOGISCHE KONSTRUKTE <, <=, >, >= || && == != ! grösser, grössergleich, kleiner “oder” ∨ “und” ∧ “gleichheit” “ungleich” “nicht” CASTS EINGABE & AUSGABE Änderung einer Variable in einen anderen Typen. cout << “a = “ << endl; cin >> a; double a = 1.5; int b; b = int (a); b = (int) a; // b=1 7/2 = 3 , 7/(double)2 = 7/2.0 = 3.5 double(7/2) = 3.0 , int(19/10.0) = 1 ENUM Enum ist ein Aufzählungstyp. Die Konstanten aus der Enum kann man im Programm verwenden. enum farbe {ROT, BLAU, GELB}; farbe f = ROT; \n \t \” //Ausgabe //Eingabe Zeilenende horizontaler Tabulator Anführungszeichen UMRECHNUNG BINÄR – DEZIMAL 13 6 3 1 1 0 1 1 Dezimalzahl durch 2 teilen und Rest notieren. Bits von unten nach oben lesen. Bsp: 13 = 1101 1001 = 1*23 + 0*22 + 0*21 + 0*20 = 8+0+0+1 = 9 while(i>10){ i--; if(i==15) i*=6;} KONTROLLSTRUKTUREN ARRAYS IF int Array mit 4 Zellen: Inhalte definieren: 0 if(a==10){ b=15; } else if(a==11) b=14; else b=10; oder kurz: a==10?b=15:a==11?b=14:b=10; FOR for(int i=0; i<10; i++) { a=a+i; } abbrechen mit break; WHILE oder //mindestens 1 x abbrechen mit break (immer nur die innere Schleife); Überspringen des Rests des Rumpfes zur nächsten Auswärtung mit continue; SWITCH switch(a) { case 15:cout<<”a=15”;break; //a==15 case 14:cout<<”a=14”;break; //a==14 default:cout<<”a!=15,a!=14”; //else } ÄQUIVALENTE STRUKTUREN Man kann verschiedene Strukturen verwenden um ein und dasselbe auszudrücken: int i=0; do { i=i+1; if (i==10) break; }while(true); //aka immer …ist äquivalent zu… for(int i=0;i!=10;i++){} BEISPIELE FÜR ENDLOSSCHLEIFEN int i=10; do { i=i+1; if (i==10) break; }while(true); for(int i=3;i!=20;i=(i+3)%300) int i=99; Bei int a[N]; muss N als const int N = 10; definiert werden. Eine const int kann während dem Programmablauf nicht geändert werden. Ein Array beginnt immer mit a[0] und endet mit a[N-1] Übergibt man ein Array einer Funktion, ist das wie “Call by Reference”. Das Original-Array wird verändert. ARRAYS UND POINTER Arraynamen sind Pointer! Bei der Definition eines Arrays wird Speicherplatz für eine bestimmte Anzahl Objekte reserviert. Die Arrayvariable zeigt auf das erste Objekt dieses Speicherplatzes. Darum sind folgende Ausdrücke identisch: while(b<20){ b++; } do { b--; } while(b!=15); 2D Array (Matrix): 3D Array: int a[4]; a[0] = 1; int a[4] = {1,2,3,4}; int b[3][2] = {{1,2},{ int c[x][3][x-1] = {{{ int c[10]; int* pc; pc = c; //Array definieren //Pointer definieren //Pointer zeigt auf Array pc[3] = 10; ↔ c[3]=10; ↔ *(pc+3)=10; Folgendes generiert auch ein Array mit Platz für 3 Integer: int * a = new int[3]; a[0] = 3; //ohne Stern * (!) delete [] a;//Speicher wieder freigeben STRUCTURES Structs werden vor der main() Funktion definiert. struct point { int x, y; double gamma; }p,q; //p,q schon definiert Neuer “point” definieren: point p; Variabeln in struct definieren: p.x = 2; Schnell initialisieren: p = {1,2,0.75}; Rest wird mit 0 aufgefüllt: q={1}->q={1,0,0}; Zuweisung: p=q ist gleichbedeutend mit p.x=q.x; p.y=q.y; p.gamma=q.gamma; Falsch: struct falsch {int i;falsch x;}; Strichpunkt am Ende nicht vergessen: struct point {int i;double y;}; FUNKTIONEN IN STRUCTS Die Konstruktor-Funktion wird bei der Generierung eines neuen Structs aufgerufen. struct Bar { Bar() { //Konstruktor }; } Funktionen können auch ausgelagert werden: struct Bar { void bier(); void Bar::bier(){ }; Aufrufen der Funktion: } bqm; bqm.bier(); FUNKTIONEN CALL BY REFERENCE (DYNAMISCH ) Ermöglichen Aufspaltung des Programms in Unterprogramme. Aufbau: rückgabewert funktionsname (argument) {funktionskörper} Der Rückgabewert ist immer nur 1 Element und kann von beliebigem Typ sein. Falls die Funktion keine Rückgabe hat, schreibt man void. Der Funktionsname darf nicht mit einer Zahl beginnen. Nur ein Wert als Rückgabewert. Workaround: Structs PROTOTYP EINER FUNKTION Falls eine function g die function f benötigt, muss f vorab definiert sein: int f(int,int,int); //prototyp int g(int x, double y){… a=f(x)+y …}; int f(int x, int y, int z){… b=g(z) …}; Hier wird statt der Referenz ein Pointer auf das Objekt übergeben. Damit auf das Objekt zugegriffen werden kann, muss der übergeben Pointer dereferenziert werden. void swap3(int* a, int* b) {int c=*a; *a=*b; *b=c;} int main(){ int x=2, y=3; swap3(&x,&y);… //vertauschen Aufruf: swap3(pa,pb); wenn pa, pb Pointer sind oder swap3(&a,&b); wenn a, b keine Pointer sind. Mann kann auch einer Funktion einen dereferenzierten Pointer übergeben: void swap4(int a, int b){…} Aufruf: swap3(*pa, *pb); Das wirkt dann aber wie call-by-value und macht hier keinen Sinn. REKURSION CALL BY VALUE Wenn eine Funktion aufgerufen wird, warden by call-byvalue die Argumente auf den Stack kopiert. void swap1(int a, int b) {int c=a; a=b; b=c;} int main(){ int x=2, y=3; swap1(x,y);… int n; int& nr = n; nr und n können nun als Synonyme verwendet werden. nr und n sind aliases. //bringt nichts Auch wenn ich a und b innerhalb der Funktion tausche, bleiben x und y in main() noch gleich. -> structures Der r etu rn Wert der Funktion ruft die Funktion selber wieder auf. Dabei müssen die Abbruchbedingungen definiert werden. Es wird bis zur Abbruchbedingung in die Rekursion hineingegangen und dann von innen aufgelöst. int fakultaet( int n ) { if(n==1) return 1; return n * fakultaet(n-1);} Aktive Funktion belegt Speicherplatz im Stack. Dieser kann dadurch überfüllt werden. struct st{int a,b}; STRINGS st swap1(int a, int b){ int c=a; a=b; b=c; st ret={a,b}; return ret;} C – STRINGS int main(){ int x=2, y=3; st out = swap1(x,y); x=out.a; y=out.b;… nicht: char text[5]= ”hallo”; char* text = “hallo”; char* str = new char[4]; str[0] = ‘C’; char text[] = “hallo”; //auto: ‘/0’ char text[] = {’h’,’a’,’l’,’l’,’o’,’/0’} char text[6]= “hallo”; //mit structure //kopieren -> x,y CALL BY REFERENCE (STATISCH) Die Variabeln werden nicht kopiert. Es wird eine Referenz auf das Objekt gemacht. Nun geht das Vertauschen einfach: void swap2(int& a, int& b) {int c=a; a=b; b=c;} int main(){ int x=2, y=3; swap2(x,y);… //vertauschen Ein Funktionsaufruf swap2(x,y);vertauscht x und y. Es warden von der Funktion nur die Adressen der Variabeln genommen und diese vertauscht, was Rückwirkung hat. Ein String wird als Pointer auf ein Array von chars definiert. Das Array hat die Länge n+1. a[n] = O Bit, Abschluss, ‘/0’ text[i] (int) text[i] liefert das i-te Zeichen liefert den ASCII Code des i-ten Zeichens int strlen(char text[]) liefert Länge ohne “\0” char(65) -> A (aus ASCII Tabelle) liest n-1 Zeichen von der Tastatur in str[] und hängt “\0” an: void cin.getline(char str[], int n); C++ - STRINGS Sehe Standard Template Library (STL) Referenzen zeigen auf fixe Adressen, Pointer können ihre Adresse ändern. ASCII TABELLE 00-31: NUL,… 48-57: 0-9 32: SPACE 65-90: A-Z 97-122: a-z 127: DEL POINTER Ein Pointer speichert und zeigt auf eine Adresse. Wenn an dieser Adresse ein Objekt liegt, dann zeigt der Pointer auf das Objekt. Pointer braucht (meist) 4Bytes Speicherplatz. Dereferenzierungsoperator *: Zugriff auf Inhalt der Speicherzelle auf die der Pointer verweist. Referenzierungsoperator &: Ermittlung der Adresse einer Variable. int* pa = 0; int *pb; pb = NULL; //Pointer der auf int //zeigen soll 0 (NULL) //setzen. int a = 3; pa = &a; //pa unzeigen, o.k. *pb = 3; //b über pb ändern -> FEHLER Wenn ein Zeiger auf NULL zeigt, ist er unbrauchbar, er muss zuerst wieder umgezeigt warden. int **ipp; ist ein Pointer auf einen Pointer Man kann * und & beliebig kombinieren. Ein Paar Beispiele: int i=1, *ip, **ipp; //ip zeigt auf i ip = &i; ipp = &ip; //ipp auf ip **ipp=6; //i,*i und**ipp=i=6 cout<<i; //-> 6 cout<<*&**&ip; //-> 6 *& „heben“ sich auf Achtung: && ist ein logisches Konstrukt. Beispielaufgaben: Welchen Typ haben die Variabeln? a = &b; b = c[12]||(2>=1); -> bool* a; bool b; bool c[20]; a = b.c*3.14; b.a = char(b.c%2) == c; //==,% beachten -> double a; char c; -> struct foo {bool a; int c;} b; else { .. delete[] d; …} //kein Programmabbruch Array vergrössern/Löschen: int*aa = new int[2*n]; //neues Array aa for (int i=0;i<n;i++) //kopieren von a { //nach aa aa[i]=a[i]; } delete[] a; //löschen von a a=aa; n=2*n; aa=NULL; //umzeigen … POINTER AUF ARRAYS Wie schon erwähnt, sind Arraynamen Pointer, genauer gesagt Pointer auf eine konstante Adresse, auf das erste Element des Arrays. int* const ip //const. Zeiger (Array) const int* ip //Zeiger auf const. int const int* const ip //beides POINTER AUF STRUCTS struct triple {int a,*b,c;}trp; *(trp.b) //deref von (trp.b) *trp.b //wie oben triple* tp = new struct triple; tp->a=10; //a in tp beschreiben (*tp).a=10; //wie oben tp->getBeer(); //siehe Klassen ARGUMENTE VON MAIN int main(int argc, char** argv){ for(int i=0;i<argc;i++)cout<< … //output argc ist die Anzahl der Parameter argv ist das Array dieser Parameter atoi()konvertiert ascii to integer, atof() to float EINFACHE LISTEN Man generiert Elemente (Knoten), die über einen Pointer auf das jeweils nächste Element zeigen. struct tNode{ int key; tNode* next; }; tNode *list = 0; b.b = &b; b.dat = “jawohl, genau du”; -> struct ff{ff* b; char* dat;} a; Der Zeiger auf den Anfang der Liste ist der Anker (aka root). a.b[2] = 5.0f; a.a = (a.b[2] > a.b[1]); -> struct sss{float b[5]; bool a;} a; Die neue Liste braucht 2 neue Pointer, last zeigt auf den letzten Knoten, node auf den aktuellen Knoten: DYNAMISCHE SPEICHERALLOZIERUNG Mit new kann man einen Pointer und neues Objekt erzeugen. Bei Arrays muss die Grösse nicht mehr const sein. double *dp = new double; int *ip = new int(3); //*pd=3 int *pe = new int[n]; //dyn. Array Falls kein Speicher vorhanden -> Fehler. Abhilfe: double *dp = new (nothrow) double [n]; if (dp == 0) cout<<”Error mem alloc”; NEUER KNOTEN AM ENDE tNode *node; node = new tNode; //neuer Knoten node->key = 5; //Daten einfügen node->next = NULL; //verankern if (list == 0){ //Liste leer? last = node; //neuer letzer Knoten list = node;} //Anker auf Anfang else { last->next = node //anhängen last = last->next //neuer l. Knoten Beim Aufbau der List mit Anhängen der Knoten am Anfang geht man gleich vor, nur umgekehrt: node->next=list (nicht mehr 0) verknüpft den neuen Knoten, … counter++;top=top->next;} top=tmp; tmp=0; //top = top(alt) return counter;} LINEARE SUCHE HEAP Suchfunktion nach Key k, mit Zähler count (Rückgabewert): Im Gegensatz zum Stack bietet der Heapspeicher viel mehr Möglichkeiten. Elemente des Heapspeichers haben einen Schlüssel und können zu jedem Zeitpunkt entnommen werden. Dafür braucht er auch viel mehr Platz. Im Heap werden oft Bäume verwendet. int search(int k, tNode* list){ tNode *node = list; //node von Aufbau int count = 1; //Start bei 1 while(node){ //nicht leer? if(node->key == k) return count; node = node->next; count++;} NEUER KNOTEN DAZWISC HEN p zeigt auf ein Element in der Liste. Neues Element mit Key k und Zeiger q hinter dem Element auf das p zeigt einfügen: void insert(int k, tNode* p){ tNode *q = new tNode; q->key = k; q->next = p->next; p->next = q;} BINARY TREES Eine Liste mit jeweils 2 Nachfolgern. Dem obersten Knoten sagt man Wurzel (root). Alle Knoten (node) die am Ende des Baums hängen werden Blatt (leaf) genannt. Höhe eines Baums = maximale Anzahl Knoten zwischen root und leaf. Folgendes Element links: kleiner als Knoten, rechts grösser. struct tNode { int key; tNode *left, *right; }; DOPPELT VERKETTETE LISTEN EINFÜGEN Jeder Knoten speichert nicht nur den Nachfolger sondern auch den Vorgänger. Füge neuen Knoten mit Key k ein. Rekursive Methode. void insert(tNode *p, int k){ if(p==0{ p = new tNode; p->key = k; p->left = NULL; p->right = NULL;} else if(p->key > k) insert(p->left,k); else insert(p->right, k);} struct tNode { int key; list *next, *prev; }; Vorteil: Man kann einfacher einfügen und löschen. Nachteil: Die Datenstruktur ist komplexer. DYNAMISCHE DATENSTRUKTUREN STACK Der Stack funktioniert nach dem LIFO-Prinzip. Last In First Out bedeutet, dass alte Daten immer weiter nach unten geschoben werden, weshalb der Stack auch als Stapel oder Kellerspeicher bezeichet wird. Man kann den Stack als einfach verkettete Liste betrachten. Jedes Element hat einen Wert val und einen Zeiger next: void push(int value){ element* el; el=new element; el->val=value; if (top){el->next=top; top=el;} else {top=el; el->next=0;}} bool isempty(){ if (top) return 0; else return 1;} int pop(){ if (top){ int ret; element* del; ret=top->val; del=top; top=top->next; delete del; return ret;} else{ cout<<"The stack is empty."<<endl; return -1;}} int size(){ int counter=0; element* tmp=top; while (top){ Aufruf: tNode *root = NULL; insert(root,2); SUCHEN Iterative Methode. Rückgabewert ist ein Pointer. tNode* search(tNode *root, int k){ tNode *p = root; while(p){ if(p->key == k) return p; if(p->key > k) p = p->left; else p = p->right;}} Aufruf: tNode *x = search(root,2); LÖSCHEN Beim Löschen eines leafs: 1.) Pointer auf leaf 2.) Ast = NULL 3.) über Pointer leaf löschen 4.) Pointer löschen Beim Löschen eines Knotens (kein leaf) gibt es mehr Schwierigkeiten: 1.) Man muss einen Folgeknoten auswählen, um den gelöschten Knoten zu ersetzen. Man könnte zählen, welcher Ast mehr Knoten hat und dann diesen nehmen, um der Degenerierung vorzubeugen. 2.) Bevor man diesen jedoch anhängt und den anderen Knoten löscht, sollte man den inneren Ast des Astes, der nach oben gezogen wird umhängen (Elementweise, Rekursion?). DEGENERIERUNG - Fügt man die Knoten sortiert ein, so degeneriert der Baum zu einer einfach verketteten Liste, wobei jeder 2te Pointer ein Nullpointer bleiben wird. - Lösung: zufälliges einfügen. Best case: height ≈ log2(#Knoten) Worst case: height = #Knoten = Länge der einfachen Liste -> bei Degenerierung O - NOTATION friend classname f2(classname);//(1) void f3(int); //(2) } objectnames; Prototypen von… (1) friend Function (2) Elementfunktion KONSTRUKTOREN / DESTRUKTOREN zur Abschätzung von Laufzeit: for(int i=0; i<N; i++) { //𝑂(𝑁) for(int j=0; j<M; j++) { //𝑂(𝑀) cout << “hello”; //𝑂(1) cout << endl; //𝑂(1) } } //Laufzeit = 𝑂(𝑀∗𝑁∗(1+1)) = 𝑂(𝑀∗𝑁) Allgemein: Konsanten << N wegstreichen. Wenn die Länge halbiert wird (Suchalgorithmen) meistens 𝑂(𝑙𝑜𝑔2(𝑛)). -> unten am Rumpf / im .cpp File definieren -> zur Erzeugung / Zerstörung der Objekte classname::classname(int a, int b){ v1 = a; v2 = b/1.5;} Destruktor bei dyn. alloziertem Speicher aka variablen sind Pointer und im Konstruktor wird new verwendet: classname::~classname(){ delete var1; delete var2;} MIT O RECHNEN 𝑓(𝑛) ∈ 𝑂(𝑔(𝑛)) : f wächst höchstens so schnell wie g Formal: lim | 𝑓(𝑛) 𝑛→∞ 𝑔(𝑛) | < ∞ (ev. Bernoulli benutzen) Oder: f wächst asymptotisch mindestens so schnell wie g, falls es ein c>0 gibt, so dass 𝑔(𝑛) ≤ 𝑂(𝑓(𝑛)) In diesem Fall schreibt man 𝑔 ≤ 𝑂(𝑓) Bsp: 𝑓1 = 𝑛3 , 𝑓2 = 7𝑛2 ist 𝑓1 < 𝑂(𝑓2 ) lim | 𝑛3 𝑛→∞ 7𝑛5 𝑛 | = lim ( ) = ∞ 𝑛→∞ 7 → stimmt nicht BEISPIELE VON LAUFZE ITEN Algorithmus Bubblesort Selectionsort Insertionsort Mergesort Quicksort Suchen 𝑂(⋯ ) Sortieren 𝑂(⋯ ) avg case worst case 𝑁2 𝑁2 2 𝑁 𝑁2 2 𝑁 𝑁2 𝑁 ∙ log 2 𝑁 𝑁 ∙ log 2 𝑁 𝑁 ∙ log 2 𝑁 𝑁2 … in Listen, unsortierten Arrays … in sortierten Arrays … in Binärbäumen (Idealform) Standardkonstruktoren können ohne Parameter aufgerufen werden. Er setzt dann Defaultwerte ein. Entweder wird ein zweiter Konstruktor definiert, der keine Parameter braucht, oder aber im Prototyp werden die Defaultwerte bestimmt. classname::classname(){ //Version 1 v1 = 0; v2 = 0;} //mit Überladen classname(int=0,int=0); //Version 2 classname::classname(int a, int b){ v1 = a; //oben: Prototyp v2 = b/1.5;} //mit Defaultwerten ELEMENTFUNKTIONEN best case 𝑁 𝑁2 𝑁 𝑁 ∙ log 2 𝑁 𝑁 ∙ log 2 𝑁 𝑁 log 2 𝑁 log 2 𝑁 KLASSEN Eine Klasse ist eine Datenstruktur. Man kann damit Daten und Funktionen (= Methoden) verwalten. Zugriffsrechte: public: von überallher zugreifbar, wo Klasse bekannt ist. private: (default) nur von innerhalb der Klasse und von friends zugreifbar protected: nur abgeleitete Klassen dieser Basisklasse haben Zugriff class classname { private: int v1; double v2; public: classname(int,int); //constructor ~classname(); //destructor friend float f1(int,int); //(1) Im Rumpf steht der Prototyp (2 oben), unterhalb wird die Funktion wie folgt definiert: void classname::f3(int a){ cout<<a*v1*v2<<endl;} float f1(int a, int b){ int c = a*b; return a*c+b*c;} classname f2(classname input){ classname output; output.v1 = input.v1*2; output.v2 = input.v2/3.14; return output;} Die Funktion f3 ist eine Funktion der Klasse, f1 und f2 sind befreundete Funktionen, die kein clname::fx haben. AUFRUFE classname oname; //-> standardkonstrukt classname oname(4,1.3); classname oname = classname(4,1.3); oname.f3(6); //-> object function x=f1(7,1); //-> friend function this ist ein Zeiger auf das aktuelle Objekt, so kann gezeigt werden, dass man nicht auf eine globale/lokale Variable, sondern auf diejenige in dieser Klasse zugreifen möchte: classname::classname(int x, int y) { this->x = x; this->y = y; } Gibt es eine Variable x und y innerhalb des Objekt wie auch ausserhalb oder werden die Parameter wie im Beispiel auch so benannt, braucht man this, um Verwechlungen zu verhindern. KOPIERKONSTRUKTOR Normalerweise wird ein Objekt bitweise kopiert, dies kann zu Fehlern führen: Pointer der Kopie zeigen nämlich auf denselben allozierten Speicher wie die Pointer des Originalen. Wird nun eines der beiden gelöscht, und hat die Klasse einen ordentlichen Destruktor wird der allozierte Speicher freigegeben. Der Pointer des bestehenden Objekts zeigt nun auf Speicher, welcher nicht mehr vom Programm gebraucht wird. Zusätzlich problematisch ist, dass jegliche Änderungen des einen Objekts am Array auch für das andere Objekt gilt. Um dies zu verhindern gibt es einen Kopierkonstruktor. Er soll definieren was passiert wenn kopie = orig; aufgerufen wird. Dort muss neuer Speicher alloziert werden, das Array kopiert und schliesslich muss der Pointer des neuen Objekt auf das neue Array zeigen: (‚this‘ wird nur zur Hervorhebung benutzt, ist nicht nötig hier) cname::cname(const cname& copy){ this->size = copy.size; int *arr = new int[this->size]; for(int i=0;i<this->size;i++){ arr[i]=copy.pointer[i];} this->pointer = arr; } FRIEND CLASS Friend class B einer Klasse A, d.h. ihre Funktionen können auf die privaten Elemente der Klasse A zugreifen: class classA; //forward declaration class classB{ int b1, b2; //per default private public: void function (classA); //(1) }; class classA{ float a1, a2; public: friend class classB; //(2) }; In diesem Beispiel braucht es (2), damit function (1) aus B private Elemente von A verwenden kann. A muss vor (1) schon deklariert sein, deshalb die forward delcaration. ÜBERLADEN VON OPERAT OREN Durch Operatorfunktionen kann man Operatoren wie +, -, *, %, … überladen, d.h. ihnen je nach Parameteranzahl, -typ eine neue Funktion zuweisen. Global: a + b ist eine Kurzform für a.operator+(b); oder operator+(a,b); In einer Klasse: Die Funktion operator+() erlaubt es dem Operator + neue Bedeutung zuzuweisen. Bsp: tBruch tBruch::operator+(long s){} tBruch tBruch::operator+(char s){} Je nachdem ob s ein long oder ein char ist wird in der Klasse tBruch die entsprechende Operatorfunktion aufgerufen. Aufruf: bruch = bruch.operator+(s); bruch = bruch + s; REIHENFOLGE DER OPER ANDEN Die Reihenfolge der Operanden kann eine wichtige Rolle spielen. In diesem Bsp hat der Operator nur ein Argument, dieses kann float oder int sein, aber nicht ein Objekt der Klasse tRect. rectb=recta*1.5; rectc=recta*3; //o.k rectd=3*recta //->Fehler Abhilfe durch neue globale Operatorfunktion (mit 2 Operanden) Reihenfolge nun egal. tRect::operator*(float s,tRect r){…} VERERBUNG class classname : public baseclass {…}; Die Klasse classname besitzt alle Elemente der Basisklasse baseclass plus die neuen Elemente. Als Beispiel die Basisklasse Polygon und ihr „Kind“: class tPolygon{ protected: int width, height; public: void set_values (int a, int b){…}; }; class tRectangle : public tPolygon{ public: int area(){return(width*height);} }; BASISKLASSEN -POINTER & VIRTUELLE FUNKTIONEN BSP: class Animal{ public: virtual void eat(){ cout << “irgendein Tierlaut”;} }; class Cat : public Animal{ public: void eat(){ cout << “miau”;} void schnurren(){cout <<..} }; //im main: Animal *p = new Cat(); p->eat(); Ein Basisklasse-Pointer hat auch die Möglichkeit auf eine abgeleitetes Objekt zu zeigen. Allerdings entstehen da folgende Probleme: Der Pointer “kennt” nur die Elemente und Methoden der Basisklasse (z.B. p->schnurren(); ergibt einen Fehler) Haben Basis- und abgeleitete Klasse Funktionen mit denselben Namen und Übergabewerten, wird die Funktion der Basisklasse aufgerufen. D.h. ohne virtual oben, würde “irgendein Tierlaut” ausgegeben Um nun die Funktion eat() der Katze aufzurufen benötigt die Basisklasse Animal ein “virtual” Tag. Es ist auch möglich die Funktion in der Basisklasse nicht zu implementieren, falls jede abgeleitete Klasse die Funktion eat() enthält: virtual int eat()=0; Der Nachteil hier ist allerdings, dass die Klasse Animal so abstrakt wird und nicht mehr instanziert werden kann und jede Tochterklasse muss unbedingt eine Funktion eat() haben. Der Vorteil ist, dass ein Animal-Pointer sowohl auf Hünde wie auch auf Katzen zeigen kann und die Funktion eat() kennt. OOP Objektorientiertes Programmieren = Arbeiten mit Klassen Idee: Daten und Funktionen (=Methoden) in einem problemspezifischen Objekt zusammenfassen -> Klassen Die STL ist eine Bilbliothek von Templates, unter anderem für Container. Sequenzielle Container: vector, list, deque. Assozialtver Container: map. Neben Container gibt es auch Iteratoren, Algorithmen u.v.m. Sequentielle Container Die Elemente von seq. Cont. sind als lineare Folge angeordnet: vector, deque list Assoziative Container Elemente von assoziativen Containern haben einen Schlüsselwert und sind in einem Binärbaum sortiert: set, multiset, map, multimap, bitset Iteratoren Bsp für Random Access Iteratoren: vector, deque, string Bsp für bidirectional access Iteratoren: list, map Arbeiten mit mehreren Files: Übersichtilicher, auf verschiedene Personen aufteilbar. Headerfiles (.h) und Programfiles (.cpp) werden verbunden, zu Objektfiles (.o) kompliliert, gelinkt und ausgeführt. STRING TEMPLATES Ein Template ist wie eine Schablone für eine Klasse oder eine Funktion. Definiert man sein Klasse oder Funktion als Template, braucht man diese nicht mehrere Male zu schreiben. Funktion ohne Template: void swap(int& a, int& b) {int c=a; a=b; b=c;} //nur für int Funktion mit Template: //nicht nur für int template <class T> void swap(T& a, T& b) {T c=a; a=b; b=c;} Klasse ohne Template: //nur für int class tStack { int index; int *s; public: tStack(){s=new int[256];index=0} ~tStack(){delete[] s;} Bool push(int); }; Klasse mit Template: //nicht nur für int template <class T> class tStack { int index; T *s; public: tStack(){s=new T[256];index=0} ~tStack(){delete[] s;} Bool push(T); }; T kennzeichent den noch unbekannten Typ; überall wo dann dieser Typ vorkommen soll wird T geschrieben. Anstelle von <class T> kann man auch <typename T> schreiben. STL :: STANDARD TEMPLATE LIBRARY Header: Beschreibung: #include <string> -> siehe c++ strings. Nur so viel Speicher wie nötig. Elementfunktionen: length() liefert die Länge des Strings insert(n,s) fügt den String s an Position n ein erase(p,n) entfernt n Zeichen ab Position p find(s) Liefert die Position von s begin(),end() end() zeigt hinter das letze El. rbegin(),rend() bei Rückwärts-Iteration Beispiele: string s(“154la6“); // constructor s.erase(3,2); // -> 1546 int pos = s.find(“46“); // -> 2 string::iterator i; for (i=s.begin();i!=s.end();i++) {cout *i;} Achtung: Positionen ab 0: “dochno“ -> p(‘h‘) = 3 VECTOR Header: #include <vector> Beschreibung: Dynamisches Array. Es kann leicht am Ende eingefügt / entfernt werden. Elemente im Speicher zusammen. Elementfunktionen: empty(),size(),resize(n) capacity(),reserve(n) vec_name[],at,front,back push_back(s),pop_back() begin(),end(),rbegin(),rend() merge,sort,reserve Beispiele: vector<int> vec1; // empty vector vector<int> vec2(5); // [0,0,0,0,0] vector<int> vec3(3,5); // [3,3,3,3,3] vec2.at(6)=12; // prüfen ob möglich vec2[7]=10; // nicht möglich vec.resize(8); vec2[7]=10; vec2.reserve(20) // Grösse erhöhen // jetzt möglich // Kapazität neu 20 LIST Header: #include <list> Beschreibung: Doppelt verknüpfte Liste; elemente irgendwo im Speicher; über Zeiger verbunden. Einfügen irgendwo gut möglich. Elementfunktionen: empty(),size(),resize(n) front,back push_back(s),push_front(s),pop_back() begin(),end(),rbegin(),rend() merge,sort,reserve Beispiele: list<int> list1; // constructor list<int>::iterator it; // iterator for(int i=0;i<10;i++) {list1.push_back(rand()%9+1);} for(it=list.begin();it!=list.end();it++) {cout<<*iter<<” ”;} // anzeigen DEQUE Header: #include <deque> Beschreibung: Double ended queue. Wie Vektor, aber Einfügen an beiden Enden. Für Warteschlangen geeignet (FIFO), besser als List. Elementfunktionen: empty(),size(),resize(n) capacity(),reserve(n) deque_name[],at,front,back push_back(s),push_front(s)... begin(),end(),rbegin(),rend() Beispiel (FIFO-Puffer): deque<int> puffer; for(int i=0;i<100;i++) //füllen {puffer.push_front(i);} do //leeren {puffer.pop_back(); }while(puffer.size()); MAP Header: #include <map> Beschreibung: Assoziativer Container: gut zum suchen. Mit Key, der das Element eindeutig identifiziert. Für Telefonbücher u.a. Elementfunktionen: empty(),size(),max_size() map_name[] find,count,find,erase,lower_bound begin(),end(),rbegin(),rend() Beispiele (Autokennzeichen): map<string,string> Kfz; Kfz[”ZH”] = ”Zuerich”; Kfz[”AG”] = ”Aargau”; cout<<Kfz[”AG”]<<endl; cout<<Kfz.size()<<endl; if (Kfz.find(”KL”)==Kfz.end()) {cout<<“not in map“<<endl;} Beispiele (bidirektionale Iteratoren): map<Key,T>::iterator it; it->first; //key value it->second; //mapped value for(it=Kfz.begin();it!=Kfz.end();i++){} STACK Header: #include <stack> Beschreibung: Container Adapter stellen für andere Container spezielle Schnittsteler zur Verfügung. Funktionen push_back() und pop_back() und pop() müssen vorhanden sein. -> Stacks können auf vector, deque und list basieren. QUEUE / PRIORITY QUEUE Header: #include <queue> Beschreibung: Queue kann auf deque (default) und list aufbauen. Bei Priority queue haben die Elemente zusätzlich eine Priorität. Priority queue kann auf vector und deque aufbauen. FIFO. #include <queue> #include <list> queue<int,list<int>> qu;//queue auf list