informatik_zsf_2008

Werbung
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
Herunterladen