Design Frage



  • Hallo Leute.

    Nähmen wir mal an wir sollen eine Klasse Lineare_Funktion schreiben. Wie würdet ihr das realisieren? Also würdet ihr die nötigen Infos so oder so übergeben?
    1.)

    Lineare_Funktion L("f(X) = 2X - 5");
    

    2.)

    Lineare_Funktion L(m,b); // f(X) = mx + b
    

    Wie man die zweite Lösung umsetzen könnte ist mir klar. Ich könnte z.b als m 2 und als b -5 übergeben und er würde beim errechnen von y, 2*X+(-5) rechnen, aber ich habe jetzt einmal die erste Variante gemacht und soweit so gut fertig, sieht sicher bisjen komisch aus und ist auch sicher eine schlechte Lösung, wie würdet ihr da vorgehen?

    Mein Code:
    Main.cpp

    #include <iostream>
    #include "Lineare_Funktion.hpp"
    
    using namespace std;
    
    int main()
    {
        Lineare_Funktion L("f(X) = 2X - 5");
        cout << L.Errechne_Y(5) << endl;
        cout << L.Errechne_X(2) << endl;
        return 0;
    }
    

    Lineare_Funktion.hpp

    #ifndef Lineare_Funktion_HPP
    #define Lineare_Funktion_HPP
    
    #include <string>
    
    class Lineare_Funktion
    {
        private:
            double y,m,x,b;
            char bop;
    
        public:
            Lineare_Funktion();
            Lineare_Funktion(std::string g);
    
            double Errechne_X(double y);
            double Errechne_Y(double x);
    };
    
    #endif
    

    Lineare_Funktion.cpp

    #include "Lineare_Funktion.hpp"
    using namespace std;
    
    Lineare_Funktion::Lineare_Funktion() {}
    
    Lineare_Funktion::Lineare_Funktion(string g)
    {
        int fxpos = g.find("X",0); // ist das X in f(X)
        int xpos = g.find("X",fxpos+1); // ist das X für das der wert eingesetzt wird.
        int gpos = g.find("=",0);
        int bpos = g.find_last_of(" ");
    
        int blub = g.find("+",0);
        if (blub >= 0)
        {
            this->bop = '+';
        }
        else
        {
            this->bop = '-';
        }
    
        string m = g.substr(gpos+1 , xpos-gpos-1);
        string b = g.substr(bpos,*(g.end())-bpos);
    
        const char* lol = m.c_str();
        for (unsigned int i=0 ; i<m.length() ; i++)
        {
            for (int z='0' ; z<='9' ; z++)
            {
                if (z == lol[i]) { this->m = lol[i] - '0'; }
            }
        }
    
        const char* rofl = b.c_str();
        for (unsigned int i=0 ; i<b.length() ; i++)
        {
            for (int z='0' ; z<='9' ; z++)
            {
                if (z == rofl[i]) { this->b = rofl[i] - '0'; }
            }
        }
    }
    
    double Lineare_Funktion::Errechne_X(double y)
    {
        // 5 = 2X - 5
        double tmp = y;
        if (bop == '+') { tmp -= b; }
        else { tmp += b; }
        tmp /= m;
        return tmp;
    }
    
    double Lineare_Funktion::Errechne_Y(double x)
    {
        double tmp = this->m * x;
        if (bop == '+') { tmp += b; }
        else { tmp -= b; }
        return tmp;
    }
    

    Also meine Fragen nun, wie könnte man die erstere Idee besser realisieren und welche von den beiden ist aus der Sicht eines Programmierers am besten? (Ich denke mal die 2te^^). Ahja und gäbe es Fälle, wo die Erstere besser wäre?

    Mfg Design Frager

    PS: Ja ich hab noch nicht viel Ahnung von der STL, aber der Code funktioniert so, solange m oder b nicht mehr wie 1 stelle haben. Und find() gibt mir wenn er nichts findet oder es nicht klappt immer -1 zurück. Ist das so normal oder sollte man sich da nicht drauf verlassen?



  • Also ich würde die Koeffizienten der Funktion lieber als einzelne Parameter übergeben, das ist einfacher in der Handhabung.

    Ansonsten solltest dur dir mal ansehen, wie man Eingaben richtig parst - bei entsprechend einfachen Formaten könnten schon die iostreams etwas erreichen, wenn es komplexer wird, empfehle ich boost::spirit.

    PS: string::find liefert eigentlich npos zurück, das ist eine unsigned-Konstante mit dem Wert -1. Die Zuweisungen zu int könnten trotzdem nicht portabel sein.



  • Der Rückgabewert von std::string::find ist std::string::size_type . Er kann direkt nach std::string::npos getestet werden. Das es bei dir -1 ist, ist implementationsspezifisch und sollte so nicht verwendet werden.

    std::string test("ABCDFG");  // <= kein 'E'
    
    	std::string::size_type p1 = test.find('E');  // 'alt' C++
    	auto p2 = test.find('E');  // C++0x
    
    	if(p1==std::string::npos)
    	{
    		// ...
    	}
    
    	if(p2==std::string::npos) // gleich zu p1
    	{
    		// ...
    	}
    


  • Noch ein paar kleine Tips. CStoll schon geschrieben hat ist die explizite Lösung mit der direkten Angabe der Werte besser. Zum einen geht das schneller, vor allem bei der internen Verwendung. Als zweites bedeutet so eine Stringübergabe einen kompletten Parser einzubauen, wenn das vernünftig gemacht werden sollte. Nichts ist schlechter als einen fehlerhaften String anzugeben, die Klasse nimmt daraus irgendwelche Werte und rechnet etwas was der Anwender eigentlich garnicht will. So ein Parser gehört zur äußeren Schnittstelle (zum Benutzer) damit der sehen kann wenn dort Fehler sind.

    In der Klassendefinition sind auch noch die Variablen x und y deklariert. Die braucht man dort überhaupt nicht. Die werden in den Berechnungsfunktionen von den lokalen Parametern überlagert und mit ihnen wird garnicht gearbeitet.

    Dann noch die Membervariable bop . Das Vorzeichen sollte in b schon automatisch beinhaltet sein. Bei der Berechnung noch eine zusätzliche if-Abfrage ist unnötig und kostet nur. Zusätzlich wird die Klassendefinition dadurch unsicher - Was wenn b einen negativen Wert besitzt ?

    Als letztes fehlt noch bei der Division der test auf 0.0. Ist m=0.0 , so kann keine Errechne_Y funktioniern.

    class Lineare_Funktion
    {
        private:
            double m, b;
    
        public:
            Lineare_Funktion();
    
            double Errechne_X(double y);
            double Errechne_Y(double x);
    };
    
    double Lineare_Funktion::Errechne_Y(double x)
    {
        if(m==0.0)
        {
            // throw ????
        }
    
        return (x-b)/m;
    }
    


  • class Lineare_Funktion
    {
        private:
            double m, b;
    
        public:
            Lineare_Funktion();
    
            double Errechne_X(double y);
            double Errechne_Y(double x);
    };
    
    double Lineare_Funktion::Errechne_Y(double x)
    {
        if(m==0.0)
        {
            // throw ????
        }
    
        return (x-b)/m;
    }
    

    Meinst du nicht eher

    return (m*x)-b;
    

    ? 😃

    Und warum soll bei m = 0.0 eine Exception geworfen werden? Wenn m = 0.0 ist, dann ist es eine Funktion mit 0 Steigung. Es muss nur extra behandelt werden, da sonst überall immer 0 wäre. In dem fall, dass m = 0.0 bei f(X) = 2X - 5 ist hieße die Funktionsgleichung nur noch Y = -5. Das ist aber kein Grund für eine exception^^



  • alf123 schrieb:

    Meinst du nicht eher

    return (m*x)-b;
    

    ? 😃

    Ich denke, er hat nur X und Y verwechselt 😉

    Und warum soll bei m = 0.0 eine Exception geworfen werden? Wenn m = 0.0 ist, dann ist es eine Funktion mit 0 Steigung. Es muss nur extra behandelt werden, da sonst überall immer 0 wäre. In dem fall, dass m = 0.0 bei f(X) = 2X - 5 ist hieße die Funktionsgleichung nur noch Y = -5. Das ist aber kein Grund für eine exception^^

    bei "f(X)=2X-5" kann m nicht 0 sein, außer du hast Müll gebaut beim Parsen der Funktionsgleichung. Probleme entstehen, wenn du eine Funktionsgleichung "f(X)=0X+5" gegeben hast und jetzt ausrechnen willst, an welcher Stelle f(X)=17 gilt - daher die Exception bzw. Sonderbehandlung.



  • Auf Melmac scheint man komisch zu rechnen...

    Die Funktion heißt nur falsch, gemeint war Errechne_X(double y) , also quasi die Umkehrfunktion. Deine Formel ist aber auch falsch, sinnvoller wäre:

    return m * x + b
    

    Das Problem an m = 0.0 ist, dass die entstehende Funktion weder linear noch umkehrbar ist, was beides eine Exception rechtfertigt.



  • 😕 So viel Gedöns 😕 Es kreisste der Elefant (*) und gebar eine Maus! 😃

    Wenn es nur darum geht, eine einfache Funktion z=function(x,y) zu realisieren, brauche ich keine Klasse dafür. Wozu die Übergabe von Strings, wenn es sich um Zahlenwerte handelt? Falls man vorher Strings hatte, wandelt man die in Zahlenwerte um. Oder muss vorher noch geprüft werden, ob man überhaupt rechenbare Zahlenwerte hat? Alles ist native C oder darf man das nicht mehr verwenden? 😕

    basta!

    (*) Das ist der mit dem langen Rüssel und den grossen Ohren



  • bmario schrieb:

    Die Funktion heißt nur falsch, gemeint war Errechne_X(double y) , also quasi die Umkehrfunktion.[/cpp]

    Jup - Etwas genauer hinsehen beim posten sollte auch mal bei mir helfen.

    Errechne_Y(double x)
    y = m*x + b

    Errechne_X(double y)
    x = (y-b) / m

    Und da sich die Prozessoren heute auch bei 3 GHz immer noch weigern durch 0 zu teilen, muss man das eben besonders behandeln. Das throw ist übrigens nur angedeutet, es wären auch andere Arten der Fehlerrückmeldung möglich.

    berniebutt schrieb:

    Wenn es nur darum geht, eine einfache Funktion z=function(x,y) zu realisieren, brauche ich keine Klasse dafür.

    Jein. Es geht natürlich auch in einer einfachen Funktion wie z.B.
    double Geradenfunktion(double m, double b, double, x)
    Aber die Klasse wird vielleicht dazu benötigt einen Grafen zu zeichnen. Dann ist die Benutzung der Klasse wesentlich einfacher, da ich die Geradendefinition nur einmal anlegen muss und in der eigentlichen Zeichenfunktion mich auf das ausrechnen des Funktionswertes konzentrieren kann - m und b müssen nicht mitverwaltet werden sondern nur die Gerade selber. Und jetzt weiter: Ich will auch mal eine Parabel zeichnen. Schwups, nur eine andere Klasse in die Zeichenfunktion einbauen und es flutscht. Bei einer einfachen Funktion muss in der Zeichenfunktion auch die Geradenparameter mit verwalten. Stelle ich von einer Geraden auf eine Parabel um, so muss ich in der Zeichenfunktion wesentlich mehr anpassen. Wirklich sinnvoll wird es dann mit einer virtuellen Basisklasse Basis_Funktion von der weitere Funktionen wie auch die Lineare_Funktion abgeleitet wird. Da brauche ich nur eine Zeichenfunktion definieren und alle von Basis_Funktion abgeleiteten Funktionen können automatisch gezeichnet werden.



  • tunichtgut schrieb:

    Wirklich sinnvoll wird es dann mit einer virtuellen Basisklasse Basis_Funktion von der weitere Funktionen wie auch die Lineare_Funktion abgeleitet wird. Da brauche ich nur eine Zeichenfunktion definieren und alle von Basis_Funktion abgeleiteten Funktionen können automatisch gezeichnet werden.

    Oder noch besser, man programmiert die Zeichne-Funktion als Funktionstemplate, nennt double berechne_Y(double x) in double operator()(double x) um und kann dann sowohl die Klasse als Funktor, beliebige andere Funktionsklassen und auch C-Funktionen übergeben 😉



  • tunichtgut schrieb:

    Aber die Klasse wird vielleicht dazu benötigt ...

    Davon war hier keine Rede, nur von einer linearen Funktion! 😞
    Wenn man so an das Design eines Programmes herangeht, müsste man schon viele Variable sofort in eine Klasse packen. Man weiss ja oft nie, ob man noch mehr damit anfangen will. Beispiel: KontoStand < kritisch --> Online-Order an Bank erteilen? 😕


Anmelden zum Antworten