[Aufgabe] Klasse - Zeit
-
Guten Tag,
Ich habe das Programm nun soweit fertig,
doch an dieser Stelle ist momentan glaube ich noch etwas falsch:
Zeit::Zeit(int stunde, int minute) { getstunde (); //warum kann ich hier jetzt keine 2 Argumente übergeben? getminute (); //somit kann ich die richtige Stundeanzahl usw. ja nicht bestimmen getstundedezimal (); }
eine Frage noch dazu:
int getstunde () const;
das const bedeutet hier ja nur das der Rückgabewert der Funktion const ist oder?
die übergebenen Werte ( z.b minute oder stunde ) sind dann nicht const und dürfen innerhalb der Funktion verändert werden (auserhalb bleiben diese so wie sie waren)#include <iostream> using namespace std; //a) Klasse erstellen class Zeit { private: int stunde, minute; public: Zeit (); Zeit (int stunde, int minute); //Prototypen int getstunde () const; int getminute () const; double getstundedezimal () const; friend ostream& operator << (ostream& out, Zeit const& time); }; //b) Definieren der Methoden und deklarieren der überladene Operatoren int Zeit::getstunde()const { int var_stunde = stunde + minute/60; return stunde; } int Zeit::getminute()const { int var_minute = minute%60; return minute; } double Zeit::getstundedezimal () const { int minutengesamt=0; double stundendezimal; minutengesamt = minute + stunde*60; stundendezimal = double(minutengesamt)/60; return stundendezimal; } Zeit::Zeit() { getstunde (); getminute (); getstundedezimal (); } Zeit::Zeit(int stunde, int minute) { getstunde (); //warum kann ich hier jetzt keine 2 Argumente übergeben? getminute (); //somit kann ich die richtige Stundeanzahl usw. ja nicht bestimmen getstundedezimal (); } //Operatoren Prototype //Optional //Zeit summiere_zeiten (Zeit const& a, Zeit const& b); //wie in Aufgabe verlangt Zeit operator+ (Zeit const& a, Zeit const& b); int main() { //Variablen vom Typ Zeit definieren Zeit zeit1 = Zeit(3, 83); Zeit zeit2 (1, 52); Zeit zeitsumme; zeitsumme = zeit1 + zeit2; //Optional //zeitsumme = summiere_zeiten (zeit1, zeit2); //Ausgabe cout << zeitsumme << '\n'; system("pause"); return 0; } //Operatoren Definition für Klasse Zeit //Optional /*Zeit summiere_zeiten (Zeit const& a, Zeit const& b) { return Zeit(a.getstunde() + b.getstunde(), a.getminute() + b.getminute()); }*/ //wie in Aufgabe verlangt Zeit operator+ (Zeit const& a, Zeit const& b) { return Zeit(a.getstunde() + b.getstunde(), a.getminute() + b.getminute()); } ostream& operator << (ostream& out, Zeit const& time) { //Optional //return out << time.getstunde() << "h " << time.getminute() << "m = " << time.getstundedezimal(); //wie in Aufgabe verlangt return out << time.stunde << "h " << time.minute << "m = " << time.getstundedezimal(); }
-
Im sog. Default-Konstruktor:
Zeit::Zeit()
solltest du Standardwerte festsetzen. Deine Funktionsaufrufen sind dort vollkommen falsch!
Zeit::Zeit() { stunden = 0; minuten = 0; }
. Dann soll im Parameter-Konstruktor stunden und minuten übergeben werden. Im einfachsten Fall (minuten <= 60) wäre das dann einfach:
Zeit::Zeit(int stunden, int minuten) { this->stunden = stunden; this->minuten = minuten; }
Hier siehst du jetzt, dass es sinnvoll ist, bei Membervariablen ein m_ vor dem Namen (Bezeichner) zu setzen, um zu verhinden "this->" schreiben zu müssen.
Da du aber nicht vom idealen Fall ausgehen kannst, d.h. wenn der Wert von "minuten" > 60 ist, musst du dem entsprechend deine Stunden anpassen. Deswegen setzt man ja auch die Membervariablen stunden und minuten als private, damit man solche Fälle abhandeln kann und dem Benutzer keinen direkten zugriff auf die Variablen erlaubt. (Stell dir vor jemand gibt 71 für minuten an und in den restlichen Berechnungen gehst du davon aus, dass es max. 60 sind!)
Zeit::Zeit(int stunden, int minuten) { this->minuten = minuten % 60; this->stunden = this->stunden + (minuten - this->minuten) / 60 }
Der module-operator gibt ja den Rest einer Ganzzahl Division zurück (12 % 5 = 2). Wenn wir jetzt mal annehmen, dass minuten = 128 ist und stunden = 1. Dann würde beim module-operator ja stehen: 128 % 60 = 8. für stunden ergäbe sich dann 1 + (128 -
/ 60. Und das sind, korrekterweise, 3. Dem nach sind 1h und 128min das selbe wie 3h und 8min.
Wenn minuten jetzt <= 60 ist, ergibt sich auch ein korrekter Wert.
minuten = 33;
stunden = 3;
33 % 60 = 33min;
3 + (33 - 33) / 60 = 3h.Damit ist der Konstruktor fertig! (Und ja diesen Vorgang nennt der Autor deiner Aufgabe "Norminierung")
int Zeit::getstunde()const { // int var_stunde = stunde + minute/60; return stunde; }
getstunde() soll einfach nur den Wert der Membervariable stunde zurück geben. Sonst nichts! (minuten / 60 ist hier vollkommen überflüssig, da du die Werte im Konstruktor norminiert hast
d.h. minuten ist < 60)
Auch wenn bsw. 1h und 15 min Dezimal 1.25h wären, wird das bei einer Ganzzahl-Division niemals rauskommen
int Zeit::getminute()const { int var_minute = minute%60; return minute; }
So ist die Funktion murks. Es ist in diesem Fall allerdings die Frage ob nach der Aufgabenstellung die stunden auch in Minuten umgerechnet werden sollen oder einfach nur die Membervariable minuten gewollt ist.
int Zeit::getminute() const { return (stunden * 60 + minuten); }
So wäre es, wenn die Stunden berücksichtigt werden sollen.
int Zeit::getminute() const { return minuten; }
so, wenn nicht
double getstundedezimal () const;
Hier sollst du das erste mal keine Ganzzahl zurück geben. Das ist wichtig!
double Zeit::getstundedezimal () const { int minutengesamt = minute + stunde * 60; double stundendezimal = double(minutengesamt)/60; return stundendezimal; }
Du weißt aber schon, wie Prozentrechnung geht?
http://de.wikipedia.org/wiki/Prozentrechnung nochmal angucken!
Grundwert: 60
Prozentwert: minuten
Prozentsatz: (minuten / 60) * 100
Soweit so gut!double Zeit::getstundedezimal () const { return static_cast<double>(minuten) / 60.0 + stunden; }
static_cast macht in dem Fall aus der Ganzzahl die minuten bezeichnet wird, einen Fließkomma-Typen (double), um dem PC zu sagen, dass hier keine Ganzzahl-Division stattfinden soll, sondern eine Fließkomma-Division.
friend std::ostream& operator << (std::ostream&, Zeit const&);
std::ostream& operator << (std::ostream& out, Zeit const& time) { // return (out << time.stunden << " Stunden und " << time.minuten << " Minuten ergeben als StundeDezimal: " << time.getstundedezimal()); return (out << time.stunden << "h " << time.minuten << "min (" << time.getstundedezimal() << "h)"); }
Das auskommentierte ist so, wie es vom Aufgabensteller erwartet wurde. Sieht allerdings ziemlich doof aus
"2h 45min (2.75h)" sieht besser aus als "2 Stunden und 45 Minuten ergeben als StundeDezimal: 2.75"
Dann musst du jetzt nurnoch operator + überladen!
-
Hmm operator+ vergessen ... okay. Der ist ganz einfach:
friend Zeit operator+(Zeit const& lhs, Zeit const& rhs);
friend Zeit operator+(Zeit const& lhs, Zeit const& rhs) { return Zeit(lhs.m_hour + rhs.m_hour, lhs.m_minutes + rhs.m_minutes); }
Um die Verteilung der minuten auf Stunden kümmert sich ja dein Parameter-Konstruktor, da diese sich ja um die Norminierung kümmert! Also einfach eine neue Instanz erzeugen und fertig
Und dein Beispielprogramm:
#include <iostream> #include "Zeit.hpp" int main() { // Variablen vom Typ Zeit definieren Zeit time_first(3, 83); Zeit time_second(1, 52); // operator + ausprobieren Zeit time = time_first + time_second; // operator << ausprobieren std::cout << "First time: " << time_first << "\nSecond time: " << time_second << "\n=======================\n" << time << std::endl; std::cin.get(); }
Guck mal im F.A.Q. warum std::system("pause") nicht verwendet werden sollte. Also das Beispielprogramm sollte mit deiner Klasse dann problemlos Funktionieren und die Aufgabe sollte fertig sein
-
So, nun bist du ja zu den Klassen fortgeschritten...
Im Konstruktor einer Klasse (der immer denselben Namen wie die Klasse hat) mußt du dafür sorgen, daß das Klassenobjekt richtig initialsiert wird (anhand der Übergabeparameter).
Zeit::Zeit(int stunde, int minute) { this->stunde = stunde; this->minute = minute; }
Da deine internen Klassenvariablen (member) bei dir genau so heißen, wie die Übergabeparameter, muß man zur Unterscheidung noch "this" verwenden (welches implizit auf das eigene Klassenobjekt zeigt).
Alternativ könnte man auch die Übergabeparameter anders benennen.
Eine zweite Möglichkeit der Initialisierung sind Intialisierungslisten:
Zeit::Zeit(int st, int min) : stunde(st), minute(min) // <- Initialisierungsliste { }
So, nachdem das Objekt richtig initialisiert ist, benötigt man jetzt noch Zugriffsfunktionen. Dabei gibt es sogenannte Getter und Setter.
Mit einem Getter kann man Daten auslesen und mit einem Setter Daten setzen.
Da das Auslesen das interne Objekt nicht verändert, kann man die Getter-Methode als konstant deklarieren:
class Zeit { public: ... int getstunde() const; }; int Zeit::getstunde() const { return stunde; }
Dies gibt dem Compiler die Möglichkeit, Fehler zu erkennen und Optimierungen auszuführen.
Ein Setter dagegen soll ja gerade interne Daten (member) verändern, daher darf eine Setter-Methode nicht als konstant deklariert werden, z.B.
void Zeit::setze_zeit(int st, int min) { stunde = st; minute = min; }
Um noch mal zu deinem Konstruktor zurückzukommen: innerhalb des Konstruktors Zeit::Zeit macht es keinen Sinn eine Getter-Methode aufzurufen, da dort ja erst die Member initialisiert werden müssen.
Die Getter und Setter sind für die Anwendung der Klasse wichtig:Zeit zeit(9, 30); // Konstruktor aufrufen cout << zeit.getstunde() << ':' << zeit.getminute(); // Getter aufrufen zeit.setze_zeit(13, 55); // Setter aufrufen cout << zeit; // alternative und bessere Ausgabe mittels des Stream-operators <<
Die Konstantheit der Getter-Funktionen spielt beim Zugriff eine wichtige Rolle.
Wenn du ein konstantes Objekt hast, dann kann man für dieses Objekt auch nur konstante Methoden aufrufen, andernfalls gibt der Compiler einen Fehler aus.const Zeit zeit(0, 0); cout << zeit.getstunde(); // ok zeit.setze_zeit(11, 55); // Fehler, da setze_zeit das Objekt 'zeit' verändern würde
Wenn du nun die Setter-Methode setze_zeit(...) noch so anpaßt, daß sie noch die Zeitnormalisierung durchführt, dann kannst du diese Methode einerseits im Konstruktor noch aufrufen (um das Zeitobjekt gleich richtig zu initialisieren) und andererseits kann man diese Methode auch noch nachträglich aufrufen, um die Zeit neu zu setzen.
Alternativ würde sich hierfür auch der operator = anbieten:const Zeit& Zeit::operator =(const Zeit &zeit) { setze_zeit(zeit.stunde, zeit.minute); return *this; // das geänderte Objekt wieder als (konstante) Referenz zurückgeben // (Dereferenzierung, weil this ein Zeiger ist) } // Aufruf im Hauptprogramm Zeit zeit(10, 20); zeit = Zeit(12, 0); // Zuweisung (operator =)
So, ich hoffe, du bist jetzt nicht vollkommen durcheinander gebracht worden und kommst hiermit ein Stückchen weiter.
-
ahhh... okey so langsam geht mir ein Licht auf.
einige Sachen werd ich erst noch etwas verdauen müssen, aber im Großen und Ganzen ist das jetzt klarer.@ (D)Evil
sicher das es so heissen muss?this->stunden = this->stunden + (minuten - this->minuten) / 60
und nicht so:
this->stunden = stunden + (minuten - this->minuten) / 60
double Zeit::getstundedezimal () const { return static_cast<double>(minuten) / 60.0 + stunden; }
diese Variante sieht sehr elegant aus (werde ich auch in zukunft so machen), aber wäre meine Variante nicht auch gegangen?
double Zeit::getstundedezimal () const { int minutengesamt = minute + stunde * 60; double stundendezimal = double(minutengesamt)/60; return stundendezimal; }
und wozu benötigt man hier Prozentrechnung, soweit ich weis behersche ich eigentlich das Prozentrechnen ich frage mich bloß wo es hier eine Anwendung finden soll?!
Das ist doch hier dreisatz:
60 min - 1 h
90min - x
x = 1h*90min/60min = 1,5hoder stehe ich gerade auf der Pipeline?
System("pause"); werde ich in zukunft nicht mehr nutzen.
Nachteile: - es wird eine Shell geöffnet ( exploits könnten dadurch entstehen)
- es ist ein OS abhängiger Befehl ( je nach OS müssen andere Parameter gesetzt werden.in zukunft werde ich std::cin.get(); verwenden.
Ich danke euch allen recht herzlich für die Geduld und auch dafür das ihr euch die Zeit nehmt einem Anfänger wie mir die Programmiersprache näher zu bringen.
hier nochmal den geänderten Quellcode:
#include <iostream> using namespace std; //a) Klasse erstellen class Zeit { private: int stunde, minute; public: Zeit (); Zeit (int stunde, int minute); //Prototypen int getstunde () const; int getminute () const; double getstundedezimal () const; friend ostream& operator << (ostream& out, Zeit const& time); }; Zeit::Zeit() { stunde = 0; minute = 0; } Zeit::Zeit(int stunde, int minute) { //Normierung this->minute = minute%60; this->stunde = stunde + (minute-this->minute)/60; //oder mit Bezeichner m_ ( brauch man den typ der variablen bei dem Membervariablen bezeichner überhaupt?) //int m_minute = minute%60; //int m_stunde = stunde + (minute-m_minute)/60; } //b) Definieren der Methoden und deklarieren der überladene Operatoren int Zeit::getstunde()const { return stunde; } int Zeit::getminute()const { return minute; } double Zeit::getstundedezimal () const { return static_cast<double>(minute) / 60.0 + stunde; } //Operatoren Prototype //Optional //Zeit summiere_zeiten (Zeit const& a, Zeit const& b); //wie in Aufgabe verlangt Zeit operator+ (Zeit const& a, Zeit const& b); int main() { //Variablen vom Typ Zeit definieren Zeit zeit1 = Zeit(3, 83); Zeit zeit2 (1, 52); Zeit zeitsumme; //Operator + zeitsumme = zeit1 + zeit2; cout << "First time: " << zeit2 << "\nSecond time: " << zeit1 << "\n=======================\n" << zeitsumme << endl; cin.get(); } //Operatoren Definition für Klasse Zeit Zeit operator+ (Zeit const& a, Zeit const& b) { return Zeit(a.getstunde() + b.getstunde(), a.getminute() + b.getminute()); } ostream& operator << (ostream& out, Zeit const& time) { return (out << time.stunde << " Stunden und " << time.minute << " Minuten = " << time.getstundedezimal() << " Stunden (Dezimal)"); }
-
double Zeit::getstundedezimal () const
{
int minutengesamt = minute + stunde * 60;
double stundendezimal = double(minutengesamt)/60;
return stundendezimal;
}Sollte auch gehen ... wobei Function-Style-Cast nicht das Wahre sind.
this->stunden = stunden + (minuten - this->minuten) / 60
Jap hast recht
Tippfehler
operator+ kannst du ruhig als friend setzen ...
Dann guck dir jetzt mal ruhig noch die sog. Initialisierungslisten an und bau das auch ein. Achja und ein Parameter-Konstruktor mit Default-Belegung für alle Parameter kannst du auch als Default-Konstruktor ansehen
Also:
public: Zeit (int stunde = 0, int minute = 0);
Dann kannst du
Zeit();
weglassen.
-
(D)Evil schrieb:
Jap hast recht
Tippfehler
Macht ja nix passiert
Gib doch zu du hast den Fehler extra eingebaut um zu testen ob ich auch aufpasse und nicht nur abtippeSpass bei Seite, das mit der Initialierungsliste werd ich auch gleich mal probieren.
jetzt mach ich aber erstma den operator+ zum friend:
(kleine Zwischenfrage: wenn ich die Klasse in der Zeit.hpp definiere muss ich ja nur in der main.cpp nur #include "Zeit.hpp" schreiben mehr benötige ich nicht oder?) vorrausgesetzt die dateien sind im selben Ordner
So nun mit Freund:
//Datei: Zeit.hpp class Zeit { private: int stunde, minute; public: Zeit (); Zeit (int stunde, int minute); //Prototypen int getstunde () const; int getminute () const; double getstundedezimal () const; friend ostream& operator << (ostream& out, Zeit const& time); friend Zeit operator+ (Zeit const& a, Zeit const& b); //oder //friend Zeit summiere_zeiten (Zeit const& a, Zeit const& b); };
-
Naja also um Linker Fehlern vorzubeugen, da es zu mehrfacher Einbindung der selben Klasse kommen kann, solltest du dir dann Include Guards angucken und evtl. auch #pragma once.
Und using namespace solltest du auch niemals im Header nutzen!
-
double Zeit::getstundedezimal () const { return static_cast<double>(minuten) / 60.0 + stunden; }
der cast ist hier nicht nötig, weil 60.0 sowieso schon ein double ist.
Th schrieb:
const Zeit& Zeit::operator =(const Zeit &zeit) { setze_zeit(zeit.stunde, zeit.minute); return *this; // das geänderte Objekt wieder als (konstante) Referenz zurückgeben // (Dereferenzierung, weil this ein Zeiger ist) }
hat es einen bestimmten grund, warum du eine konstante referenz zurückgibst?
-
Ja, um zu verhindern, daß z.B. jemand "(a=b).setze_zeit(0, 0)" schreibt (oder noch grausameren Code)!
Auch wenn meistens eine nicht-konstante Referenz zurückgegeben wird, habe ich es für meine eigenen Klassen so entschieden.
Der Rückgabetyp bei Operatoren ist ja nicht eindeutig definiert (manche geben auch einfach nichts zurück (void)).Daran braucht sich aber 'fraggelfragger' nicht zu halten...
-
es sah auch sehr gewollt aus, wollte nur sichergehen. widespread ist es ja nicht gerade.
-
Th schrieb:
Ja, um zu verhindern, daß z.B. jemand "(a=b).setze_zeit(0, 0)" schreibt (oder noch grausameren Code)!
Natürlich ist es unmöglich, absichtlich schlecht geschriebenen Code zu verhindern, und diese Art von Code kommt sicherlich nicht versehentlich (nur in solchen Fällen kommt ein Schutz in Frage) zustande. Zudem ist das jedenfalls in meinen Augen relativ harmloser Code, es ist ziemlich klar, was dieser bewirken würde. In diesem Fall habe ich immer das Gefühl, dass hier aus einer Mücke ein Elefant gemacht wird, ohne die Nachteile zu berücksichtigen.
Nicht zuletzt sollte nicht vergessen werden, dass so eine Klasse - Überraschung! - nicht kompatibel mit Standardcontainern ist.