-
Notifications
You must be signed in to change notification settings - Fork 0
Cpp Tipps
Eine tolle Übungsseite hier
Die Klassen wird in zwei Dateien aufgeteilt:
- Die Headerdatei:
In diese Datei gehören die Definitionen der Klasse und ihrer Member (Attribute und Methoden)
Bsp.:
// foo.h
#pragma once
class Foo {
public:
Foo(); // Konstruktor
~Foo(); // Destruktor
private:
// Membervariablen (Attribute)
int x;
int y;
public:
// Memberfunktionen (Methoden)
void doSomething(int someValue);
// kleine get-Funktionen können auch in der Headerdatei definiert werden
int getX() { retrun x; }
int getY() { return y; }
};
- Die Quelldatei:
In diese Datei gehören die Deklarationen der Memberfunktionen (und evtl. auch die Initialisierung der statischen Membervariablen)
Bsp.:
// foo.cpp
#include "foo.h"
void Foo::doSomething(int someValue)
{}
Sobald ihr __extra Dateien__ wie z.B. `iostream` einbeziehen (`#include`) wollt, müsst ihr das in der Headerdatei, unter der Zeile `#pragma once` machen.
Bitte vergesst außerdem nicht eine kurze Beschreibung der Klasse anzuführen. (siehe unser Ehrenkodex)
Vererbung wird in C++ ähnlich wie in Java implementiert.
Statt dem keyword extends
in Java wird in C++ ein :
verwendet.
Desweiteren muss man in C++ angeben welchen Zugriffsmodifikator (private, ...) die vererbten Sachen haben sollen. Dies wird folgendermaßen implementiert.
Dieses Beispiel verwendet zwar nur Variablen, aber bei Funktion verhält es sich im Grunde genauso
class Base {
private: // private variablen werden nicht weitervererbt
int x;
protected: // protected variablen werden weitervererbt
int y;
public: // public variablen werden weitervererbt
int z;
};
class Derived : public Base {
public:
void print() {
z = 3; // OK
y = 3; // OK
x = 3; // ERROR!
}
};
Außerdem auch wichtig: Destruktor
Eine bessere Dokumentation hier.
Das Prinzip einer abstrakten Klasse und warum wir sie brauchen sollte euch aus dem Informatikunterricht bekannt sein, deshalb werde ich nicht mehr darauf eingehen. Hier wird nur auf die Implementierung dieser eingegangen.
Um eine abstrakte Klasse in Java zu erstellen muss man der Klasse lediglich das keyword abstract
voranstellen.
In C++ ist das ein kleines bisschen komplizierter.
Damit eine Klasse in C++ abstrakt sein kann muss sie mindestens eine "rein virtuelle Funktion" besitzen.
Eine virtuelle Funktion(keine rein virtuelle) ist - stark vereinfacht gesagt - eine abstrakte Memberfunktion, die jedoch eine Definition besitzt und somit nicht in der abgeleiteten Klasse definiert werden muss.(bitte benutzt diese Definition NIEMALS, denn sie ist im Grunde genommen so falsch wie eine Aussage nur sein kann)
Das heißt im Umkehrschluss, dass jede nicht virtuelle Memberfunktion nicht in der abgeleiteten Klasse aufgerufen werden kann, ohne dass sie darin definiert wurde.
Außerdem ist eine Klasse, die eine virtuelle Memberfunktion besitzen nicht gleich abstrakt.
Eine rein virtuelle Funktion ist eine virtuelle Fuktion, die keine Definition besitzt und somit in der abgeleiteten Klasse definiert werden muss. Also im Prinzip eine uns aus Java bekannte abstrakte Methode.
// baseClass.h
#pragma once
class BaseClass
{
public:
void doSomething() {} // eine normale Funktion. Muss in dieser Klasse definiert werden.
virtual void doThis() {} // eine virtuelle Funktion. Muss ebenfalls in dieser Klasse definiert werden.
virtual void doThat() = 0; // eine rein virtuelle Funktion. Muss in der abgeleiteten Klasse definiert werden.
};
// derivedClass.h
#include "baseClass.h"
class DerivedClass : public BaseClass
{
public:
void doThat() {}
};
Erklärung anhand der Klassen BaseClass und DerivedClass (erbt von BaseClass):
// main.cpp
#include "baseClass.h"
#include "derivedClass.h"
int main()
{
BaseClass* b = new DerivedClass(); // b ist ein pointer auf eine Instanz von DerivedClass
// Das können wir machen weil DerivedClass von BaseClass erbt (Polymorphie)
delete b; // Wenn wir nun die Instanz löschen wollen wird jedoch nicht der Destruktor von DerivedClass,
// sondern der von BaseClass aufgerufen und das ist undefiniertes Verhalten. Ihr könnt euch bestimmt vorstellen warum das schlecht ist...
return 0;
}
Mir ist klar, dass das ein wenig unübersichtlich ist. Eine bessere Dokumentation findet ihr hier.
Wir benutzen Namensräume oder namespace
s um Klassen, Funktionen, etc. einem bestimmten etwas - einer Gruppierung - zuzuordnen.
Dies machen wir in erster Linie um zu verhindern, dass es zwei oder mehr im Namen identische Funktionen, etc. gibt.
Bsp.:
#include <iostream>
using namespace std;
ostream& put (char c); // Fehler: Diese Funktion gibt es schon im namespace std!!
...
Um den Fehler im obigen Beispiel zu verhindern, machen wir folgendes:
#include <iostream>
using namespace std;
namespace myFunctions
{
ostream& put(char c); // Kein Fehler: Wir sind im eigenen namespace
};
// So wird auf die Funktion zugegriffen
myFunctions::put(100);
Operatoren sind: +, -, +=, &&, ->, ==, usw. Wir kennen das Prinzip von überladenen Funktionen schon aus dem Informatikunterricht. Das sind Funktionen, die den gleichen Namen haben, sich aber in den Parametern unterscheiden Bsp.:
#include <iostream>
using namespace std;
void foo() { cout << "keine Parameter"; }
void foo(const char* s) { cout << s; }
int main()
{
foo(); // gibt aus: "keine Parameter"
foo("Ein Parameter"); // gibt aus: "ein Parameter"
}
Operatoren zu überladen ist eigentlich sehr ähnlich dazu, denn eine Operation mit einem Operator kann man auch in Form einer Funktion schreiben. Bsp.:
// in main
string a = "Ko";
string b = "ra";
string c = "mu";
a += b;
a.operator+=(c);
cout << a << endl; // gibt aus: "Koramu"
Anmerkung: Dies Funktioniert nicht direkt bei elementaren Datentypen.
Also müssen wir um einen Operator für eine Klasse zu überladen nur eine neue Methode in dieser hinzufügen. (Der Operator gilt dann auch nur für die Klasse, in der er definiert wurde. i.e. Es gelten die klassischen Scope-Regeln) Diese Methode könnte ungefähr so aussehen.
#include <iostream>
class foo
{
private:
int x;
public:
foo(int y) : x(y) {}
void operator+=(const foo& f) // const foo& ist eine Referenz auf den zweiten Summanden
{
this->x += f.x;
}
int getX() { return this->x; }
};
int main()
{
foo a(10);
foo b(20);
a += b;
std::cout << a.getX() << std::endl; // gibt aus: 30
}
Hierbei muss man sich überlegen was der Operator zurückgeben soll und was er als Parameter nehmen soll. Dies ist immer fallspezifisch zu betrachten und kann manchmal verwirrend sein, also im Zweifelsfall StackOverflow oder Ario fragen.
http://cpp-tip-of-the-day.blogspot.de/2013/11/forward-declarations-vs-includes-in.html
Summary
These are the basic rules for inclusion:
- include the headers of classes you inherit from
- always include the headers of the STL containers
- forward declare the types of static variables
- forward declare the types of the variables held by reference or by pointer
- include the header of all variables held by value
- forward declare the function parameter types and the function return types