diff --git a/2021-oop1/ku/exercise_fraction/README.md b/2021-oop1/ku/exercise_fraction/README.md new file mode 100644 index 0000000000000000000000000000000000000000..98633557fd89b24031c90a453b0c9b8c154dc695 --- /dev/null +++ b/2021-oop1/ku/exercise_fraction/README.md @@ -0,0 +1,136 @@ +# Übungsbeispiel: Bruch + +Ziel dieses Übungsbeispiels ist es, Operator-Overloading und Exception-Handling in einem kurzen Programm zu implementieren. + +### Aber um was geht es denn? + +Es soll eine Klasse `Fraction`, die einen Bruch - also eine rationale Zahl - darstellt, erstellt werden. Nach fertiger Implementierung soll mit dieser Klasse wie mit gewöhnlichen `int` oder `double` mit den Operatoren `+`, `*`, `==` ... gerechnet werden können. + +Beim Rechnen mit Brüchen ist außerdem zu beachten, dass der Nenner nie `0` werden darf. Insbesondere beim Erstellen eines neues Bruchs, aber auch beispielsweise beim Dividieren könnte es hier zu einem Problem kommen. Dafür soll eine eigene Exception `NullDivisionException` implementiert werden. + +## Klasse Fraction + +Die Klasse `Fraction` soll genau einen Bruch darstellen. Dafür werden zwei Attribute benötigt, nämlich ein Zähler (`nominator_`) und ein Nenner (`denominator_`). + +Ein Bruch soll intern nach jeder Operation immer in gekürzter Form gespeichert sein und der Nenner soll immer positiv sein. Beispielsweise `5/-15` soll als `-1/3` gespeichert werden. + +### Konstruktoren + +Ein Bruch soll auf drei Arten erstellt werden können: + +| Parameter | Beschreibung | +| --------- | ------------ | +| keiner | Der Bruch soll den Wert `0` besitzen. | +| `int` | Der Bruch soll den Wert der Ganzzahl besitzen. | +| `int`, `int` | Erster Paramter ist der Zähler, zweiter der Nenner. | + + +Im letzten Fall ist außerdem zu überprüfen, dass es zu keiner Division durch `0` kommt, ansonsten soll eine `NullDivisionException` geworfen werden. Außerdem ist zu beachten, dass der Bruch in oben beschriebenen Form gespeichert werden soll. + +*Tipp: Es ist nicht notwendig drei unterschiedliche Konstruktoren zu erstellen. Wenn man es geschickt löst, kommt man mit einem aus.* + +Copy-Konstruktor bzw. Copy-Assignment-Operator sollen als Übung explizit implementiert werden. Wichtig beim Copy-Assignment-Operator ist es, dass auch folgendes möglich sein soll: `b1 = b2 = b3`. + +*Tipp: Überlege ob es hier auch zu einer Division durch `0` kommen kann bzw. ob der Bruch gekürzt werden muss.* + +### Methoden + +- `getNominator()` + - Getter für den Zähler +- `getDenominator()` + - Getter für den Nenner +- `value()` + - gibt den Wert des Bruchs als `double` zurück +- `reduce()` + - private Methode, um den Bruch zu kürzen und den Nenner positiv zu machen + +*Tipp: Euklidischer Algorithmus. Ansonsten kann man auch alle möglichen Teiler durchprobieren, ist zwar nicht besonders effizient, aber gut genug für den Anfang.* + +### Ausgabeoperator + +Folgender Ausgabeoperator soll implementiert werden, `b1` ist ein Objekt der Klasse `Fraction`. + +- `std::cout << b1` + - `b1` soll in der Form: `<nominator_> / <denominator_>` ausgegeben werden + +### Arithmetische Operatoren + +Folgende Operationen sollten auf jeden Fall implementiert werden, wobei der Copy-Assignment-Operator bereits implementiert sein sollte. `b1`, `b2`, `b3` sind Brüche, `a` ist ein `int`. + +*Tipp: Manche Operatoren sind einfacher als Methode zu implementieren, andere wiederum müssen sogar unbedingt als Funktion implementiert werden.* + +- `b1 = b2 + b3` + - `b2` und `b3` werden zusammenaddiert, ändern sich aber nicht + +- `b1 = b2 + a` + - `b2` und `a` werden zusammenaddiert, ändern sich aber nicht (`a` ist ein `int`!) + +- `b1 = a + b3` + - `a` und `b3` werden zusammenaddiert, ändern sich aber nicht (`a` ist ein `int`!) + +- `b1 = b2 += b3` + - `b3` wird direkt auf `b2` addiert + - das Ergebnis kann an `b1` zugewiesen werden + +- `b1 = b2++` + - post-increment + - in `b1` wird der alte Wert von `b2` geschrieben + - anschließend wird `b2` um den Wert `1` erhöht + +- `b1 = ++b2` + - pre-increment + - `b2` wird um den Wert `1` erhöht + - das Ergebnis wird in `b1` geschrieben + +- `b1 = -b2` + - `b1` wird das Negative von `b2` zugewiesen + - `b2` bleibt unverändert + +- `b1 = ~b2` (bitweiser Operator) + - `b1` wird der Kehrwert von `b2` zugewiesen + - der Nenner soll weiterhin positiv sein + - `NullDivisionException` falls der Nenner `0` ist + - `b2` bleibt unverändert + +Alle weiteren arithmetischen Operatoren, wie beispielsweise `-`, `-=`, `--`, `*`, `/`... können analog implementiert werden. + +### Logische Operatoren und Vergleichsoperatoren + +Folgende Operatoren sollten auf jeden Fall implementiert werden. `b1`, `b2` sind wiederum Brüche. + +- `b1` + - ist genau dann `false` sein, wenn der Bruch den Wert `0` besitzt + - **Wichtig! `int a = b1;` darf nicht gültig sein bzw. kompilieren. Daher ist das Schlüsselwort `explicit` zu verwenden.** + +- `!b1` + - ist genau dann `true` sein, wenn der Bruch den Wert `0` besitzt + +- `b1 == b2` + - ist genau dann `true`, falls `b1` und `b2` denselben Wert besitzen + +- `b1 != b2` + - ist genau dann `true`, falls `b1` und `b2` verschiedene Werte besitzen + +- `b1 < b2` + - ist genau dann `true`, falls `b1` echt kleiner als `b2` ist + +- `b1 <= b2` + - ist genau dann `true`, falls `b1 < b2` oder `b1 == b2` + +Alle weiteren Vergleichsoperatoren können analog implementiert werden. + +## Klasse NullDivisionException + +Diese Klasse stellt eine Exception dar und soll eine Fehlermeldung repräsentieren, wenn in einem Bruch der Nenner `0` werden würde. Daher soll diese Klasse auch von `std::exception` abgeleitet werden. Innerhalb der Klasse soll es genau ein Attribut gegeben, nämlich die Fehlermeldung `message_`, als `std::string`. + +### Konstruktor + +Weiters soll es genau einen Konstruktor geben, der als einzigen Parameter den Zähler des fehlerhaften Bruchs übergibt bekommt. Anschließend soll in `message_` die Fehlermeldung ```<nominator> / 0 - Dividing through 0 not valid!\n```, wobei `<nominator>` durch den übergebenen Parameter ersetzt werden soll. Der Copy-Konstruktor und der Copy-Assignment-Operator sollen gelöscht werden. + +### Methoden + +Außerdem soll in der Klasse genau eine Methode implementiert werden, nämlich die `what()`-Methode aus der Basisklasse. Diese Methode liefert als Rückgabewert einen `const char*`, ist selbst `const`, darf selbst keine Exception werfen (`noexcept`) und überschreibt die Methode aus der Basisklasse (`override`). Hier soll genau die `message_` zurückgegeben werden, aber als eben als `const char*`, nicht als `std::string`. + +## Testprogramm + +Ein kurzes Testprogramm, dass die wesentlichen Funktionalitäten überprüft steht bereits zur Verfügung. Die noch offenen Testcases werden in Kürze noch folgen. diff --git a/2021-oop1/ku/exercise_fraction/test.cpp b/2021-oop1/ku/exercise_fraction/test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9646319088cc054c9270fc673f58d3f6f6091268 --- /dev/null +++ b/2021-oop1/ku/exercise_fraction/test.cpp @@ -0,0 +1,319 @@ +#include <iostream> +#include <cassert> +#include "Fraction.hpp" +#include "NullDivisionException.hpp" + +/** + * If you have not implemented all functionality yet, you can simply + * deselect the testcases by setting the corresponding define to 0. + * Then your programm will compile and you can test the other testcases. + */ +#define CONSTRUCTORS 1 +#define CONSTRUCTOR_NULL_DIVISION_EXCEPTION 1 +#define REDUCE_FRACTION 1 +#define COPY_ASSIGNMENT 1 +#define OUT_OPERATOR 0 // will follow soon +#define PLUS_OPERATORS 0 // will follow soon +#define PLUS_OPERATORS_WITH_INT 0 // will follow soon +#define INCREMENT_OPERATORS 0 // will follow soon +#define NEGATION_INVERSION 0 // will follow soon +#define BOOL_OPERATOR 0 // will follow soon +#define COMPARE_OPERATORS 0 // will follow soon + +void constructors(); +void constructorNullDivisionException(); +void reduceFraction(); +void copyAssignment(); +void outOperator(); +void plusOperators(); +void plusOperatorsWithInt(); +void incrementOperators(); +void negationInversion(); +void boolOperators(); +void compareOperators(); + +//------------------------------------------------------------------------------ +int main(void) +{ + std::cout << "Starting testcases!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + + constructors(); + constructorNullDivisionException(); + reduceFraction(); + copyAssignment(); + outOperator(); + plusOperators(); + plusOperatorsWithInt(); + incrementOperators(); + negationInversion(); + boolOperators(); + compareOperators(); + + std::cout << "Great! Passing all selected testcases!" << std::endl; + + return 0; +} + +//------------------------------------------------------------------------------ +void constructors() +{ +#if CONSTRUCTORS == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a; + Fraction b(5); + Fraction c(1, 10); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl; + std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl; + + assert(a.getNominator() == 0 && a.getDenominator() == 1 && + "Default constructor not correct!\n"); + assert(b.getNominator() == 5 && b.getDenominator() == 1 && + "Constructor with one paramter not correct!\n"); + assert(c.getNominator() == 1 && c.getDenominator() == 10 && + "Constructor with two parameters not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void constructorNullDivisionException() +{ +#if CONSTRUCTOR_NULL_DIVISION_EXCEPTION == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + try + { + Fraction a(2, 0); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(false && "No NullDivisionlException thrown!\n"); + } + catch (NullDivisionException& ex) + { + std::cout << "Error: " << ex.what(); + + assert(std::string(ex.what()) == "2 / 0 - Dividing through 0 not valid!\n" + && "NullDivisionException not correct!\n"); + } + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void reduceFraction() +{ +#if REDUCE_FRACTION == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + Fraction b(65, -26); + Fraction c(-123, 126); + Fraction d(-12, -144); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl; + std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl; + std::cout << d.getNominator() << " / " << d.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + assert(b.getNominator() == -5 && b.getDenominator() == 2 && + "Reducing fraction not correct!\n"); + assert(c.getNominator() == -41 && c.getDenominator() == 42 && + "Reducing fraction not correct!\n"); + assert(d.getNominator() == 1 && d.getDenominator() == 12 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void copyAssignment() +{ +#if COPY_ASSIGNMENT == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(13, -169); + Fraction b(a); + Fraction c(20); + Fraction d; + + a = c = d; + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl; + std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl; + std::cout << d.getNominator() << " / " << d.getDenominator() << std::endl; + + assert(a.getNominator() == 0 && a.getDenominator() == 1 && + "Copy-Assignment-Operator not extendable!\n"); + assert(b.getNominator() == -1 && b.getDenominator() == 13 && + "Copy-Constructor not working!\n"); + assert(c.getNominator() == 0 && c.getDenominator() == 1 && + "Copy-Assignment-Operator not working!\n"); + assert(a.getNominator() == 0 && a.getDenominator() == 1 && + "Default Constructor not working!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void outOperator() +{ +#if OUT_OPERATOR == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void plusOperators() +{ +#if PLUS_OPERATORS == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void plusOperatorsWithInt() +{ +#if PLUS_OPERATORS_WITH_INT == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void incrementOperators() +{ +#if INCREMENT_OPERATORS == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void negationInversion() +{ +#if NEGATION_INVERSION == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void boolOperators() +{ +#if BOOL_OPERATOR == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +} + +//------------------------------------------------------------------------------ +void compareOperators() +{ +#if COMPARE_OPERATORS == 1 + + std::cout << "Testcase: " << __func__ << std::endl << std::endl; + + Fraction a(2, 6); + + std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl; + + assert(a.getNominator() == 1 && a.getDenominator() == 3 && + "Reducing fraction not correct!\n"); + + std::cout << "\nAll good!\n\n"; + std::cout << "-----------------------------------------------\n" << std::endl; + +#endif +}