C++ ZUSAMMENFASSUNG Christian Forster, 28. August 2007 [email protected] Patrik Rohner, 15. Juli 2008 [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; 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 0x22ff70 0x22ff74 0x22ff78 0x22ff78 0x22ff80 0x22ff7c 0x22ff88 FLIESSKOMMAZAHLEN Float //structs, functions, enums 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 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 int main(void) { system(“pause”); return 0; Double 1bit->sign, 11bit->exponent, 52bit->mantisse Wert = (-1)S x 2(E-1023) x (1.F) } OPERATOREN VARIABELN + - / * ^ % x += i 1.1E-5 i++, i-- int 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) 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 ++b für c=6, b=6 Für weitere mathematische Funktionen: #include <cmath> fabs(), sqrt(), exp(), log(), cos(), acos() VARIABELNNAMEN LOGISCHE KONSTRUKTE Keine Leerzeichen, Satzzeichen oder Symbole Keine Zahl oder __ am Anfang case sensitivity – Gross - Kleinschreibung beachten <, <=, >, >= || && == != ! EINFACHE VARIABELN DEKLARIERE N int a,a2; int b = 10; float c = a*b – 0.5; CASTS grösser, grössergleich, kleiner “oder” ∨ “und” ∧ “gleichheit” “ungleich” “nicht” EINGABE & AUSGABE cout << “a = “ << endl; cin >> a; //Ausgabe //Eingabe Änderung einer Variable in einen anderen Typen. 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 \n \t \” ENUM 13 6 3 1 0 Enum ist ein Aufzählungstyp. Die Konstanten aus der Enum kann man im Programm verwenden. enum farbe {ROT, BLAU, GELB}; farbe f = ROT; if(f != BLAU) { }; Zeilenende horizontaler Tabulator Anführungszeichen UMRECHNUNG BINÄR – DEZIMAL 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 KONTROLLSTRUKTUREN ARRAYS IF int Array mit 4 Zellen: Inhalte definieren: 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 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++; } oder 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] = {{{ //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 STRUKTUR EN 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; while(i>10){ i--; if(i==15) i*=6;} 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. Hier wird statt der Referenz ein Pointer auf das Objekt übergeben. Damit auf das Objekt zugegriffen werden kann, muss der übergeben Pointer dereferenziert werden. 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) …}; 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);… //bringt nichts Auch wenn ich a und b innerhalb der Funktion tausche, bleiben x und y in main() noch gleich. -> structures 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 swap3(int a, int b){…} Aufruf: swap3(*pa, *pb); Das wirkt dann aber wie call-by-value und macht hier keinen Sinn. REKURSION 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. STRINGS struct st{int a,b}; C – STRINGS st swap1(int a, int b){ int c=a; a=b; b=c; st ret={a,b}; return ret;} int main(){ int x=2, y=3; st out = swap1(x,y); x=out.a; y=out.b;… 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. Referenzen zeigen auf fixe Adressen, Pointer können ihre Adresse ändern. int n; int& nr = n; nr und n können nun als Synonyme verwendet werden. nr und n sind aliases. nicht: char text[5]= ”hallo”; char* text = “hallo”; char* str = new char[4]; str[0] = ‘C’; 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 In C++ neue Klasse, benötigt #include <string> Überladene Operatoren in der string-Klasse (+,…), siehe Bsp string myname, yourfname, yourlname; myname = ”dick banger” cout << ”please enter your name ”; cin >> yourfname >> yourlname; if (yourfname +" "+ yourlname == myname) {cout << ”\n What a coincidence!”;} ASCII TABELLE 00-31: NUL,… 32: SPACE Array vergrössern/Löschen: int*aa = new int[2*n]; for (int i=0;i<n;i++) { aa[i]=a[i]; } delete[] a; a=aa; n=2*n; aa=NULL; 48-57: 0-9 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 //neues Array aa //kopieren von a //nach aa //löschen von a //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 Wenn ein Zeiger auf NULL zeigt, ist er unbrauchbar, er muss zuert wieder umgezeigt warden. ARGUMENTE VON MAIN Man kann * und & beliebig kombinieren. Ein Paar Beispiele: int i=1, *ip, **ipp; //ipp zeigt auf ip, ip = &i; ipp = &i; //ip auf i->ipp auf **ipp=6; //i und **ipp=i=6 cout<<*&**&ip; //-> 6 Achtung: && ist ein logisches Konstrukt. argc ist die Anzahl der Parameter argv ist das Array dieser Parameter atoi()konvertiert ascii to integer, atof() to float 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; b.b = &b; b.dat = “jawohl, genau du”; -> struct ff{ff* b; char* dat;} a; a.b[2] = 5.0f; a.a = (a.b[2] > a.b[1]); -> struct sss{float b[5]; bool a;} a; 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: delete dp = new (nothrow) double [n]; if (dp == 0) cout<<”Error mem alloc”; else {…} //kein Programmabbruch int main(int argc, char** argv){ for(int i=0;i<argc;i++)cout<< … //output 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; Der Zeiger auf den Anfang der Liste ist der Anker (aka root). NEUER KNOTEN AM ENDE Die neue Liste braucht 2 neue Pointer, last zeigt auf den letzten Knoten, node auf den aktuellen Knoten: tNode *node, *last; node = new tNode; //neuer Knoten node->key = value; //Daten einfügen node->next = NULL; //letzter Knoten 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, … 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){ 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){ counter++;top=top->next;} top=tmp; tmp=0; //top = top(alt) return counter;} 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 KONSTRUKTOREN / DESTRUKTOREN zur Abschätzung von Laufzeit: -> 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;} 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(𝑛)). 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) Destruktor bei dyn. alloziertem Speicher benötigt. (Variabeln sind Pointer) Konstruktor -> new verwenden: classname::~classname(){ delete var1; delete var2;} 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) friend classname f2(classname);//(1) void f3(int); //(2) } objectnames; Prototypen von… (1) friend Function (2) Elementfunktion Im Rumpf steht der Prototyp, 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 aktualle Objekt, so kann gezeigt werden, dass man nicht auf eine globale Variabel, sondern auf diejenige in dieser Klasse zugreifen möchte: classname::classname(int x, int y) { this->x = x; this->y = y; } Gibt es eine Variabel x und y innerhalb des Objekte wie auch ausserhalb oder werden die Paramerter wie im Beispiel auch so benannt, braucht man this, um Verwechlungen zu verhindern. FRIEND CLASS OOP Friend class B einer Klasse A, d.h. ihre Funktionen können auf die privaten Elemente der Klasse A zugreifen: Objektorientiertes Programmieren = Arbeiten mit Klassen Idee: Daten und Funktionen (=Methoden) in einem problemspezifischen Objekt zusammenfassen -> Klassen 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 ein Objekt 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. Arbeiten mit mehreren Files: Übersichtilicher, auf verschiedene Personen aufteilbar. Headerfiles (.h) und Programfiles (.cpp) werden verbunden, zu Objektfiles (.o) kompliliert, gelinkt und ausgeführt. 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); }; Abhilfe durch neue Operatorfunktion (mit 2 Operanden) : tRect::operator*(float s,tRect r){…} 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. VERERBUNG STL :: STANDARD TEMPLATE LIBRARY class classname : public baseclass {…}; 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. rectb=recta*1.5; rectc=recta*3; //o.k rectd=3*recta //->Fehler 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);} }; Iteratoren Bsp für Ramdom Access Iteratoren: vector, deque, string Bsp für bidirectional access Iteratoren: list, map STRING Header: Beschreibung: DEQUE #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 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() 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 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()); VECTOR Header: #include <map> Beschreibung: Assoziativer Container: gut zum suchen. Mit Key, der das Element eindeutig identifiziert. Für Telefonbücher u.a. 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); // Grösse erhöhen vec2[7]=10; // jetzt möglich vec2.reserve(20) // 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<<*it<<” ”;} // anzeigen MAP 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