klasse datum



  • hallo,
    ich möchte eine klasse datum schreiben, bin mir aber bei meinem design nicht ganz sicher:

    class Datum
    {
    		int tag, monat, jahr;
    		long yeardays(int j); //wieviele Tage hat das angegebene Jahr
    		long monthdays(int m, int j); // wieviele Tage hat der angegebene Monat
    		void date2days(const Datum &date, long &days); // Datum -> Tage seit 1.1.1900
    		void days2date(const long &days, Datum &date); // Tage seit 1.1.1900 -> Datum
    	public:
    		Datum()
    		{
    			Datum::tag=1;Datum::monat=1; Datum::jahr=1900;
    		};
    		Datum(int tag, int monat, int jahr)
    		{
    			Datum::tag=tag; Datum::monat=monat; Datum::jahr=jahr;
    		};
    		void setzeDatum(int tag, int monat, int jahr); // setzt Datum
    		void naechstesDatum(const Datum &date, Datum &nextdate); // ermitteln des nächsten Datums
    		void vorigesDatum(const Datum &date, Datum &beforedate); // ermitteln des vorigen Datums
    		void operator+(int days); //zu Datum days Tage addieren
    		void operator-(int days); //von Datum days Tage subtrahieren
    		long diff(const Datum date1, const Datum date2); // Differenz in Tagen
    		int comp(const Datum date1, const Datum date2); // date1 > date2 => -<Anz d. Tage>
    														// date1 == date2 => 0
    														// date1 < date2 => +<Anz d. Tage>
    
    };
    

    mir gehts vorallem um so fragen wie:
    die difffunktion auf das interne datum bezogen oder 2 paramter (so wie jetzt)
    die compfunktion auf das interne datum bezogen oder 2 paramter (so wie jetzt)

    das datum hab ich deswegen als referenz übergeben, weil unser professor gemeint hat er möchte nichts über einem integer per value übergeben werden sehen...

    da ich jetzt endlich mal richtig C++ lerne, möchte ich mich von anfang an auf ein "schönes" klassendesign konzentrieren..

    mfg



  • da ich jetzt endlich mal richtig C++ lerne, möchte ich mich von anfang an auf ein "schönes" klassendesign konzentrieren

    ned nuer schoen sollt es sein, auch intelligent :p

    Nur als anmerkung ... das Datum wird eigentlich von systemfunktionen abgedenkt, unter den meisten BS gibts nen Datentyp fuer.
    Der Datentyp ist meisst nen integer, der die Tage als Diff zu einen "stichdatum" zaehlt (meist 01.01.1970) ...

    Die meisten systemfunktionen (WinAPI) wollen sowas als Datum haben. Um dir den ganzen umrechnungsschrott zu sparen, wuerd ich keinen neuen Datentyp erfinden wollen, sondern einfach nen Wrapper dafuer schreiben ....
    (ne klasse die dir einen schon vorhandenen Datentyp kapselt, ihn so komfortabler macht, aber intern selber mit dem Datentyp arbeitet )

    Ciao ...



  • Ich bin (noch) kein Guru, aber vielleicht ein paar Anregungen, was mir aufgefallen ist.

    - ich würd das Schlüsselwort private: nicht weglassen, selbst wenn man kann/darf.

    - ein paar deiner Member liefern Anzahl Tage zurück, einmal als long und einmal als int. Das ist nicht durchdacht. (int comp, long monthdays)

    - mach vor deine Member Variablen ein m_ hin. Damit vermeidest du solche verwirrten Sachen wie:

    Datum(int tag, int monat, int jahr)
            {
                Datum::tag=tag; Datum::monat=monat; Datum::jahr=jahr;
            };
    

    besser:

    Datum(int tag, int monat, int jahr)
            {
                m_tag=tag; m_monat=monat; m_jahr=jahr;
            };
    

    Der Scope-Operator innerhalb der eigenen Klasse ist sowieso verpöhnt.

    Die beiden Methoden diff und comp sollten die Objekte wirklich als Referenz übergeben (wie bei vorigesDatum zB.)
    Bei kleinen Objekten is es eigentlich egal, nur wenn die größer werden verbrät der Rechner Zeit mit kopieren(und wenns ne gute Note gibt, dann mach halt einfach immer Referenz)

    Gruß
    electron



  • class Datum
    

    ok, guter name

    {
    
    int tag, monat, jahr;
    

    schlecht!
    in anbetracht der tatsache, daß die allermeinsten funktionsaufrufe
    vergleiche und op+ und op- sind, man denke nur mal an die masse an
    vergleichen, wenn mal ein datensatz in ner großen tabelle gesucht werden
    soll, sollte die interne darstellung schon ein schlichter int sein.
    in den fällen, wenn mal was auf den screen oder drucker ausgegeben werden
    soll, stört die dazu jedesmal nötige umwandlung keinen, weil drucker und screen
    unendlich lahm sind. da fällt das umwandeln gar nicht auf.
    und sauviel code wird einfacher. vergleiche einfach mal
    bool operator<(Datum const& a,Datum const& b)
    {
    if(a.year<b.year) return true;
    if(a.year>b.year) return false;
    if(a.month<b.month) return true;
    if(a.month>b.month) return true;
    return a.day<b.day;
    }
    gegen
    bool operator<(Datum const& a,Datum const& b)
    {
    return a.days<b.days;
    }

    long yeardays(int j); //wieviele Tage hat das angegebene Jahr 
            long monthdays(int m, int j); // wieviele Tage hat der angegebene Monat
    

    fast gut. funktionen sollten static sein, da sie kein datumsobjekt brauchen.
    und notwendig sind sie ja zur eingabeüberprüfung.

    void date2days(const Datum &date, long &days); // Datum -> Tage seit 1.1.1900 
            void days2date(const long &days, Datum &date); // Tage seit 1.1.1900 -> Datum
    

    unnotwendig, oder? sehe keine sinnvolle anwendung. also weg damit!
    notfalligerweise kann der user auch mal sein datum von Datum(1,1,1900)
    abziehen, um zu erfahren, wieviel zage dazwischen waren. nen feinen op-
    machste ja.
    und was soll der schwachsinnige referenzparameter?!
    void date2days(const Datum &date, long &days); ist häßlich.
    long date2days(const Datum &date); ist schön.

    das datum hab ich deswegen als referenz übergeben, weil unser professor gemeint hat er möchte nichts über einem integer per value übergeben werden sehen...

    da hat er bestimmt was anderes gemeint.

    public:
    
    Datum()
    

    ist ein defaultkonstruktor notwendig?

    {
                Datum::tag=1;Datum::monat=1; Datum::jahr=1900;
    

    gewöhne die die verwendung der initialisiererliste an. und namensgleichheit ist in der
    tat verwirrend. naja, hauptsache du machst nicht das doofe m_ davor.

    };
    

    nutzloses semikolon, wie ich zu meiner schande gestehen muß.

    Datum(int tag, int monat, int jahr) 
            { 
                Datum::tag=tag; Datum::monat=monat; Datum::jahr=jahr; 
            };
    

    hast nicht setzeDatum verwendet. wozu haste setzeDatum überhaupt? dafür ist der konstruktor
    doch da!
    hierdrinne mußte die tage seit 1.1.9000 oder 1.1.1970 in
    deinem member abspeichern.

    void setzeDatum(int tag, int monat, int jahr); // setzt Datum
    

    nutzlos und störend.
    statt d.setzeDatum(1,2,2005); schreibt man d=Datum(1,2,2005);

    void naechstesDatum(const Datum &date, Datum &nextdate); // ermitteln des nächsten Datums
    

    passend zur semantik deines op+ sollte hier statt
    void naechstesDatum(const Datum &date, Datum &nextdate);
    lieber stehen
    Datum& operator++();

    void vorigesDatum(const Datum &date, Datum &beforedate); // ermitteln des vorigen Datums
    

    entsprechend mit --

    void operator+(int days); //zu Datum days Tage addieren 
            void operator-(int days); //von Datum days Tage subtrahieren
    

    rückgabe sollte das neue datum sein. mach'..s wie bei den ints.
    hast du hier den operator+= und -= gemeint?

    long diff(const Datum date1, const Datum date2); // Differenz in Tagen
    

    long operator-(Datum const& a,Datum const& b);

    int comp(const Datum date1, const Datum date2); // date1 > date2 => -<Anz d. Tage> 
                                                            // date1 == date2 => 0 
                                                            // date1 < date2 => +<Anz d. Tage>
    

    ungewöhnlich. und operator< und operator== sind eh nützlicher, damit man die
    Datums-Objekte in stl-container stecken kann.

    };
    


  • volkard schrieb:

    wozu haste setzeDatum überhaupt? dafür ist der konstruktor
    doch da!
    ...
    nutzlos und störend.

    ???
    Das wundert mich jetzt, das von dir zu hören.
    Bei Funktionen AendereDatumWonurZeigerVerfuegbar(Datum* d)
    hilft der ctor nicht und bei so Sachen wie

    Datum d;
    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
    
        d.setzeDatum(tag, monat, jahr);
    // oder
        d = Datum(tag, monat, jahr);
    
        machWasMitDatum(d);
    }
    

    ist der erste Fall doch auch performanter.
    Begründe mal bitte deine Ablehnung der Setzefunktion.

    Jockel



  • Was genau ist da performanter?
    Datum ist ein idealer Kandidat für ein immutable-Objekt.

    Zu dem m_ für Membervariablen: Halte ich für Schrott.

    EDIT: Natürlich ist das erste performanter, weil du immer wieder ein Datum-Objekt im zweiten Beispiel kopierst. Aber wer Objekte kopiert, ist wirklich selber schuld.

    EDIT2:

    for (int i = i;  i < 10000;  ++i)
    {
        Datum d(tag, monat, jahr);
        machwas(d);
    }
    


  • Ähh? Das war doch gerade meine Kritik!
    Wenn die einzige Möglichkeit Attribute zu ändern der Konstruktor ist, dann
    muss man die Objekte wohl notgedrungen kopieren.

    Jockel



  • Dein Edit kam zu spät.
    Okay, das mit dem kopieren war Mist. Trotzdem ist der Aufruf über den Konstruktor weiterhin langsamer, oder?

    Jockel



  • Nein, denke ich nicht. Der Konstruktor setzt ja im Grunde auch nur die Werte. Vielleicht wäre er langsamer, wenn es noch mehr Datenelemente gibt wie die 3, die wir ändern wollen.
    Aber das ist IMO nichts gegen die Vorteile und Sicherheit, die unveränderliche Objekte bieten.



  • Jockelx schrieb:

    volkard schrieb:

    wozu haste setzeDatum überhaupt? dafür ist der konstruktor
    doch da!

    ???
    Das wundert mich jetzt, das von dir zu hören.
    ...
    Begründe mal bitte deine Ablehnung der Setzefunktion.

    oh, du hat mitgekriegt, daß ich speed-fanatiker bin. das ist fein.

    triviale funktionen sind in der praxis doch inline. da hat der optimierer vollen zugriff, und es ist einfach gleich, ob ich schreibe

    Datum d;
    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
        d.setzeDatum(tag, monat, jahr);
        machWasMitDatum(d);
    }
    

    oder

    Datum d;
    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
        d = Datum(tag, monat, jahr);
        machWasMitDatum(d);
    }
    

    oder

    int d[]={1,1,1900};
    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
        a[0]=tag;d[1]=monat;d[2]=jahr;
        machWasMitDatum(d);
    }
    

    es wird exaktamente der gleiche assembler-code erzeugt.
    solange es bei null-kosten bleibt, feines c++ zu nehmen, soll man das auch tun, finde ich.

    und nu zu besserem beispiel.
    das hier darf keiner schreiben:

    Datum d;
    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
        d = Datum(tag, monat, jahr);
        machWasMitDatum(d);
    }
    

    d ist viel, viel zu unlokal.
    mußt schon schreiben:

    for(i=0; i<1000000; ++i)
    {
        leseDatum(tag, monat, jahr);
        Datum d = Datum(tag, monat, jahr);
        machWasMitDatum(d);
    }
    

    aha, und jetzt apiert sogar der zweitdümmste compiler, daß hier ne returnvalue-optimierung zu greifen hat. es wird nicht ein datumsobjekt erzeugt, davon ne kopie gemacht und das original gelöscht. nee, es wird direkt in den speicher vom lokalen objet d reinkonstruiert.
    schon wieder nullkosten für abstraktion. und der trick geht auch mit größeren klassen.



  • Mal ne kurze Design-Frage, wo wir schon dabei sind:

    Sollte man bei einem Immutable-Objekt überhaupt die Operatoren, die this verändern (+=, *= etc) implementieren?



  • interpreter schrieb:

    Mal ne kurze Design-Frage, wo wir schon dabei sind:

    Sollte man bei einem Immutable-Objekt überhaupt die Operatoren, die this verändern (+=, *= etc) implementieren?

    war mit immutable gemeint, daß man schreiben sollte:

    for(i=0; i<1000000; ++i) 
    { 
        leseDatum(tag, monat, jahr); 
        Datum const d = Datum(tag, monat, jahr); 
        machWasMitDatum(d); 
    }
    

    falls immutable das ist, dann klar, hab das const da vergessen. mit const ist ja alles noch viel sicherer.
    und += und *= sind ja non-const members und würden auf dem const d gar nicht laufen, also kann man sie ruhig implementieren.



  • Wenn man ein Objekt als immutable designt, dann darf man es nach dessen Erzeugung nicht mehr verändern können. Sowas wie X::setY(int y) darf es also nicht geben. Folglich darf ich doch auch operator*= usw nicht implementieren, weil ich sonst das Objekt selbst verändern kann. Ob das Objekt nun const oder nicht is, is doch egal - auch "nicht const"-Objekte dürfen nicht mehr verändert werden.
    BTW:
    Sowas sehe ich zum 1. Mal:

    Datum const d = Datum(tag, monat, jahr);
    

    Wieso schreibst du das const nach dem Datentyp? Ist das selbe, wie const Datum d? Kann es sein, dass in der Zeile zuerst ein Temporäres Objekt erzeugt wird, das dann per CopyConstructor in d kopiert wird?



  • interpreter schrieb:

    Wieso schreibst du das const nach dem Datentyp? Ist das selbe, wie const Datum d?

    Ja.

    http://tutorial.schornboeck.net/konstanten.htm schrieb:

    Statt int const kann man auch const int schreiben, beides bedeutet das selbe. Allerdings sollte man bei der Variablendeklaration immer von Rechts nach Links lesen, so dass man float const PI so liest: (Die Variable) PI ist vom Typ const float.

    interpreter schrieb:

    Kann es sein, dass in der Zeile zuerst ein Temporäres Objekt erzeugt wird, das dann per CopyConstructor in d kopiert wird?

    siehe Humes FAQ



  • Shade Of Mine schrieb:

    interpreter schrieb:

    Wieso schreibst du das const nach dem Datentyp? Ist das selbe, wie const Datum d?

    Ja.

    http://tutorial.schornboeck.net/konstanten.htm schrieb:

    Statt int const kann man auch const int schreiben, beides bedeutet das selbe. Allerdings sollte man bei der Variablendeklaration immer von Rechts nach Links lesen, so dass man float const PI so liest: (Die Variable) PI ist vom Typ const float.

    interpreter schrieb:

    Kann es sein, dass in der Zeile zuerst ein Temporäres Objekt erzeugt wird, das dann per CopyConstructor in d kopiert wird?

    siehe Humes FAQ

    Danke.
    Da finde ich aber

    const Datum d(tag, monat, jahr);
    

    wesentlich intuitiver



  • interpreter schrieb:

    Wenn man ein Objekt als immutable designt, dann darf man es nach dessen Erzeugung nicht mehr verändern können. Sowas wie X::setY(int y) darf es also nicht geben. Folglich darf ich doch auch operator*= usw nicht implementieren, weil ich sonst das Objekt selbst verändern kann. Ob das Objekt nun const oder nicht is, is doch egal - auch "nicht const"-Objekte dürfen nicht mehr verändert werden.

    aha. und wozu macht man sowas?

    auf jeden fall ist immutable für Datum-objekte unfug. Datum muß genauso praktisch wie time_t sein. Ware einfach störend, statt d=d+14 nicht mehr d+=14 schreiben zu dürfen.


Anmelden zum Antworten