Frage zur Typumwandlung



  • Okay ich habe das mal so umgesetzt wie ihr mir das geschildert habt.

    //Definition der Klasse bruch
    class bruch
    {
    private:
    	long zaehler;
    	long nenner;
    public:
    	bruch();
    	bruch(long za, long ne);
    
    	long getzaehler(bruch& b);
    	long getnenner(bruch& b);
    	double getdezimalwert(bruch& b);
    
    	friend bruch& operator+= (bruch& lhs, const bruch& rhs);
    	friend bruch& operator-= (bruch& lhs, const bruch& rhs);
    	friend bruch& operator*= (bruch& lhs, const bruch& rhs);
    	friend bruch& operator/= (bruch& lhs, const bruch& rhs);
    	friend ostream& operator<< (ostream& os, const bruch& b);
    	friend istream& operator>> (istream& is,bruch& b);
    };
    

    Ich kann somit mit Getter methoden auf die Privaten member zugreifen.
    Ausserdem können die überladenen Operatoren über die friend Funktion auf Member der Klasse zugreifen.

    Ist dies so nun in Ordnung? Oder habe etwas noch immer nicht verstanden?

    Hierzu habe ich noch eine Frage ( sieht unten in Grün)

    //Konstruktoren
    bruch::bruch():zaehler(0),nenner(0){}
    bruch::bruch(long za, long ne):zaehler(za),nenner(ne){}
    
    //Public- und Friend-Methoden der Klasse bruch
    long bruch::getzaehler(bruch& b)
    {
    	return b.zaehler;
    }
    long bruch::getnenner(bruch& b)
    {
    	return b.nenner;
    }
    double bruch::getdezimalwert(bruch& b)
    {
    	return static_cast<double>(b.zaehler) / static_cast<double>(b.nenner);
    }
    
    bruch& operator+= (bruch& lhs, const bruch& rhs)
    {
    	lhs.zaehler *= rhs.nenner;
    	lhs.zaehler += rhs.zaehler*lhs.nenner;
    	lhs.nenner *= rhs.nenner;
    	return lhs;
    }
    
    bruch& operator-= (bruch& lhs, const bruch& rhs)
    {
    	lhs.zaehler *= rhs.nenner;
    	lhs.zaehler -= rhs.zaehler*lhs.nenner;
    	lhs.nenner *= rhs.nenner;
    	return lhs;
    }
    bruch& operator*= (bruch& lhs, const bruch& rhs)
    {
    	lhs.zaehler *= rhs.zaehler;
    	lhs.nenner *= rhs.nenner;
    	return lhs;
    }
    bruch& operator/= (bruch& lhs, const bruch& rhs)
    {
    	lhs.zaehler *= rhs.nenner;
    	lhs.nenner *= rhs.zaehler;
    	return lhs;
    }
    bruch operator+ (const bruch& lhs, const bruch& rhs)
    {
    //Kann man das hier auch etwas kürzer schreiben?
    //weil hier wird ja nun ein Objekt namens tmp erstellt, und damit gearbeitet.
    //So habe ich es schon mal gemacht: return complex(re+c.re, im+c.im);
    //Das geht hier aber nicht weil wir den Überladenen += Operator benutzen oder?
    
    	bruch tmp(lhs);
    	tmp += rhs;
    	return tmp;
    }
    bruch operator- (const bruch& lhs, const bruch& rhs)
    {
    	bruch tmp(lhs);
    	tmp -= rhs;
    	return tmp;
    }
    bruch operator* (const bruch& lhs, const bruch& rhs)
    {
    	bruch tmp(lhs);
    	tmp *= rhs;
    	return tmp;
    }
    bruch operator/ (const bruch& lhs, const bruch& rhs)
    {
    	bruch tmp(lhs);
    	tmp /= rhs;
    	return tmp;
    }
    ostream& operator<< (ostream& os,const bruch& b)
    {
    	os << b.zaehler << "/" << b.nenner << endl;
    	return os;
    }
    
    istream& operator>> (istream& is, bruch& b)
    {
    	char rechenzeichen;
    	is >> b.zaehler >> rechenzeichen >> b.nenner;
    	return is;
    }
    


  • Operator +=, -= usw. sollten nicht friend sein. Das sind Funktionen, die das Objekt verändern und Zugriff auf den Inhalt haben müssen.

    Dafür machst du +,- usw. friend und kannst dann für + usw. entweder += nutzen, oder gleich auf den Inhalt zugreifen und ein neues Objekt zurückgeben.

    bruch operator+ (const bruch& lhs, const bruch& rhs)
    {
       return bruch(lhs.zaehler+rhs.zaehler,lhs.nenner+rhs.nenner);
    }
    

    Oder du kannst das friend gleich weglassen und die Member public machen, da es da, wie im anderen thread, nicht nötig/erwartet ist.



  • long getzaehler(bruch& b) const; //const
        long getnenner(bruch& b) const; //const
    
    double getdezimalwert(bruch& b) const; //hier könnte man auch überlegen, nen template draus zu bauen... vll will man doch mal nur nen int/...
    

    operator -(bruch &rhs) fehlt noch (also die einfache negierung)...
    damit könnte man dann -= über += (-bruch) lösen

    btw:

    rechenzeichen

    das zeichen rennt wohl? ^^

    Natürlich kann man +,-,/,* so implementieren, wie das drakon schreibt - aber ich finds nicht sooo übersichtlich... (mal davon abgesehen, dass es so falsch ist, wie es drakon geschrieben hat)
    hier wäre die richtige implementation:

    bruch operator+ (const bruch& lhs, const bruch& rhs)
    {
        bruch tmp(lhs);
        return tmp += rhs;
    }
    

    die Member public machen

    Würd ich nicht machen - aber das musst du für dich selbst entscheiden...

    des weiteren solltest du deinem bruch noch nen konstruktor von int geben...
    also so was:
    bruch::bruch (int i) : zaehler(i), nenner(1) {}
    für float etc würde das zwar auch (ähnlich) gehen, aber der Sinn ist sicherlich nicht, dass man mit ungenauen Werten weiterrechnet:

    float a = sqrt(2);
    bruch b(a);
    
    bruch::bruch(float a)
    {
     //a so normieren, dass es in nen bruch passt - mit hinreichender genauigkeit - da sollte es nen algo bei wikipedia oder so geben, aber
     //float bleibt ja ne gleitkommazahl - eine, die nicht nur Elemente aus Q darstellen kann - sondern aus R
    }
    

    und += etc sollten vll wirklich nicht nur als friend sondern als member definiert werden...

    + kannst du als friend definieren, das ist aber nicht nötig - ich würd sie einfach als freie operatoren definieren...

    bb

    nachtrag:
    die skalarmultiplikation fehlt natürlich noch - also int * bruch
    das könnte man wie gesagt einerseits dadurch lösen, indem man jedem (gewünschten) typen nen standard-konstruktor für bruch gibt oder in dem man sie speziell definiert (was ich ja fast machen würde, was aber mehr arbeit wäre)...
    gleiches gilt für die addition/division/subtraktion mit nem int o.ä.

    wenn ich mir das noch mal so überlege, erscheint es mir doch sinnvoller, jedem gewünschten typ einen CTor zu geben - könnte man zwar auch über templates lösen, aber dann hätte man wieder das problem mit float und double..



  • Natürlich kann man +,-,/,* so implementieren, wie das drakon schreibt - aber ich finds nicht sooo übersichtlich... (mal davon abgesehen, dass es so falsch ist, wie es drakon geschrieben hat)
    hier wäre die richtige implementation:

    Ich habe ja geschrieben, dass er auch += nutzen kann. Das andere war ja nur ein Beispiel, dass er auch direkt auf die Variablen zugreifen kann. (Das es ein Bruch ist, habe ich gar nicht richtig gesehen. Ich habe einfach die Member genommen.. Klar kann man die nicht einfach zusammenzählen..)

    Würd ich nicht machen - aber das musst du für dich selbst entscheiden...

    Und was bitteschön stört dich an der öffentlichen Membern?



  • drakon schrieb:

    Das andere war ja nur ein Beispiel, dass er auch direkt auf die Variablen zugreifen kann.

    Sollte ja auch kein Vorwurf sein - mir ist schon klar, dass du das nur zeigen wolltest und dabei vrmtl nicht weiter drüber nachgedacht hast - weils auch keine Rolle gespielt hat... Nur, dass es ihn nicht total irritiert...

    drakon schrieb:

    Würd ich nicht machen - aber das musst du für dich selbst entscheiden...

    Und was bitteschön stört dich an der öffentlichen Membern?

    Warum soll man von außen den Bruch manipulieren können?
    Es zerstört (unsinnigerweise) die gesamte Kapselung... Beim Vector könnte es da ja sicherlich Anwendungen geben - obwohl mir keine einfällt...

    Aber beim Bruch...
    Mach doch mal nen Bsp., wo so etwas sinnvoll wäre...

    bb



  • Ich stimme unskilled hier auch zu, da es wohl selten Fälle geben wird, wo man nur den Zähler oder nur den Nenner ändert (für diese Fälle kann man immer noch Methoden anbieten). Eher wird man gerade den ganzen Bruch neusetzen oder mit den Operatoren manipulieren...



  • Nexus schrieb:

    da es wohl selten Fälle geben wird, wo man nur den Zähler oder nur den Nenner ändert

    Fällt dir denn ein Fall ein? Mir fällt echt keiner ein...
    Vll sollte man noch ne Funktion erweitern hinzufügen (und die kürzen Funktion existiert noch immer nicht - genau so, wie die Vergleichsoperatoren noch fehlen)...

    bb



  • Ich sehe nicht, warum das die Kapselung stören sollte. Wenn ich einen Zähler, oder Nenner setzen will, dann sollte der auch den Wert bekommen. Bei 0 im Nenner kann man sich natürlich streiten, aber imo hat selbst das nichts in einer Bruchklasse zu suchen.

    Aber allgemein habe ich bis jetzt noch nie eine Bruch Klasse gebraucht.

    Kürzer. Wie schon gesagt, kannst du das ja alles in einem schreiben, aber macht imo nicht wirklich Sinn. Vor allem kannst du das Verhalten so an einer Stelle abändern, ohne den + Operator anfassen zu müssen.



  • Nunja, in der Praxis wohl eher nicht. Nur wenn man gerade was ausprobiert oder so... 😉

    Ich hab mir auch vor einiger Zeit mal eine Bruchklasse geschrieben, da sind die Methoden für das Verändern von entweder Zähler oder Nenner auch noch drin. Gebracht habe ich die aber nie (könnte aber daran liegen, dass ich die gesamte Bruchklasse kaum zum Einsatz kam). 🙂

    Die Methode Simplify() kürzt den Bruch, AdaptSign() passt das Vorzeichen an (ist nicht unbedingt nötig, ich wollte halt konsistent, dass nur der Nenner negativ sein kann). Ich hab es halt so gemacht, dass nach jedem Rechenschritt gekürzt wird. Ist zwar von der Performance her nicht optimal, aber so werden die Wertebereiche weniger schnell überschritten. Also, hier mal die Schnittstelle meiner Bruchklasse, damit du dich ungefähr orientieren kannst (ist aber nicht unbedingt optimal, wie gesagt könnte man die Setter für Zähler und Nenner weglassen oder beispielsweise eine Umwandlung ToLongDouble() (oder gleich ein Template) implementieren...

    class Fraction
    {
    	private:
    		long Num;
    		long Denom;
    
    	public:
    		Fraction(long Numerator = 0, long Denominator = 1);
    
    		void SetFraction(long Numerator, long Denominator);
    		void SetNum(long Numerator);
    		void SetDenom(long Denominator);
    		long GetNum() const;
    		long GetDenom() const;
    		float ToFloat() const;
    		double ToDouble() const;
    
    		Fraction& Fraction::operator+= (const Fraction& Right);
    		Fraction& Fraction::operator-= (const Fraction& Right);
    		Fraction& Fraction::operator*= (const Fraction& Right);
    		Fraction& Fraction::operator/= (const Fraction& Right);
    
    	private:
    		void Simplify()
    		void AdaptSign()
    };
    

    Zudem sind bei mir noch globale Arithmetik- und Vergleichsoperatoren vorhanden. Ich hab auch Funktionen für kleinstes gemeinsame Vielfache und grössten gemeinsamen Teiler geschrieben, die werden auch intern für das Kürzen verwendet.

    const Fraction operator+ (const Fraction& Frac);
    const Fraction operator- (const Fraction& Frac);
    
    const Fraction operator+ (const Fraction& Left, const Fraction& Right);
    const Fraction operator- (const Fraction& Left, const Fraction& Right);
    const Fraction operator* (const Fraction& Left, const Fraction& Right);
    const Fraction operator/ (const Fraction& Left, const Fraction& Right);
    
    bool operator<  (const Fraction& Left, const Fraction& Right);
    bool operator<= (const Fraction& Left, const Fraction& Right);
    bool operator== (const Fraction& Left, const Fraction& Right);
    bool operator>= (const Fraction& Left, const Fraction& Right);
    bool operator>  (const Fraction& Left, const Fraction& Right);
    bool operator!= (const Fraction& Left, const Fraction& Right);
    
    long GreatestCommonDivisor(long Integer1, long Integer2)
    long LeastCommonMultiple(long Integer1, long Integer2)
    


  • drakon schrieb:

    Ich sehe nicht, warum das die Kapselung stören sollte. Wenn ich einen Zähler, oder Nenner setzen will, dann sollte der auch den Wert bekommen.

    Aber eben genau darum geht es: Wann willst du einzeln einen Nenner oder Zähler setzen, wenn nicht bei der Konstruktion? Und selbst wenn, was würde gegen Methoden dafür sprechen?

    drakon schrieb:

    Bei 0 im Nenner kann man sich natürlich streiten, aber imo hat selbst das nichts in einer Bruchklasse zu suchen.

    Ja, ich persönlich habe es halt lieber, die Klassen sicherer zu machen. Also schauen, dass man eine 0 als Nenner gar nie zulässt.



  • Nexus schrieb:

    drakon schrieb:

    Bei 0 im Nenner kann man sich natürlich streiten, aber imo hat selbst das nichts in einer Bruchklasse zu suchen.

    Ja, ich persönlich habe es halt lieber, die Klassen sicherer zu machen. Also schauen, dass man eine 0 als Nenner gar nie zulässt.

    wobei das auch erst zu nem fehler führen würde, wenn man sich den bruch als dezimalwert ausgeben lassen würde... und da kann man ja nen != 0 reinschreiben - sonst sollte man ja nie durch den nenner dividieren müssen!?

    bb



  • Nein, eigentlich nicht. Aber ich finde es besser, man behandelt den Fehler dort, wo er zu Stande kommt. Ansonsten kann es mühsam werden, ihn zu lokalisieren...



  • Nexus schrieb:

    Nein, eigentlich nicht. Aber ich finde es besser, man behandelt den Fehler dort, wo er zu Stande kommt. Ansonsten kann es mühsam werden, ihn zu lokalisieren...

    Naja - da es aber nirgendwo passieren dürfte, würd ichs auch nirgendwo prüfen... Ist ja auch egal - ist sicherlich Geschmackssache, ob man nun Setter baut oder die Member öffentlich macht - oder man nur Getter hat (wie ich es ja machen würde)...

    bb



  • Bin grad dabei dies zu machen.
    +=
    *=
    usw. mach ich dann auch noch

    Hallo,
    du solltest mal boost/operators.hpp anschauen. Der generiert dir automatisch aus z.B. aus dem +=operator den +operator durch ableiten.

    Gruß


Anmelden zum Antworten