mit Klassen arbeiten - wer kann mir helfen?



  • ganz einfach: Du schreibst keine Klasse, mit der man Brüche addiert, subtrahiert usw. sondern du schreibt eine Klasse, die Brüche repräsentiert. So dass Bruch zu einem neuen Datentyp wird.

    ah, ich muss sagen, langsam aber sicher beginne ich zu verstehen (hoff ich mal ;)) hm, ich glaub ich werd mir das mit nmans "Bruechen" mal zu Herzen nehmen und versuchen... auch wenn ich das schlimmste befürchte. Danke für die Hilfe!



  • Schrei einfach, wenn du nicht mehr weiter weist. Vorab: wenns ans Kürzen geht,
    schau dir mal Euklids Algorithmus zum finden des ggT (einfach googln, findest du bestimmt 🙂 )



  • So ist's eleganter und schneller:

    #include <iostream>
    #include <cmath>
    using namespace std;
    
    class mathe
    {
    public:
        int zahl1;
        int zahl2;
    
        mathe()
        {
            zahl1 = 0;
            zahl2 = 0;
        }
    
        void ausgeben()
        {
            cout << zahl1 << endl;
            cout << zahl2 << endl;
        }
    
        void addieren(int *z2)
        {
            zahl1+=*z2;
        }
    
        void ergebnis()
        {
            cout << zahl1 << endl;
        }
    };
    
    int main()
    {
        mathe m;
    
        m.zahl1 = 7;
        m.zahl2 = 4;
        m.ausgeben();
        m.addieren(&m.zahl2);
        m.ergebnis();
    
        return 0;
    }
    


  • Schrei einfach, wenn du nicht mehr weiter weist. Vorab: wenns ans Kürzen geht, schau dir mal Euklids Algorithmus zum finden des ggT (einfach googln, findest du bestimmt)

    danke für den Tipp!



  • so, ich hab mir mal Gedanken über Bruchrechnung gemacht und hab auch versucht eine Klasse für Brüche zu schreiben. Vorab: das Programm funktioniert soweit und rechnet auch korrekt, trotzdem hab ich das Gefühl, dass es nicht wirklich objektorientiert programmiert ist, weil man es glaub ich genauso gut auch mit Funktionen hätte lösen können:
    ich poste es einfach mal und wer Lust (und die Nerven dazu hat, ist nämlich etwas länger und sicherlich geht es auch kürzer) kann es sich ja mal anschauen und mir vielleicht Verbesserungen (sicherlich;)), Hinweise, Fehler usw. sagen, wäre sehr nett! 🙂

    #include <iostream>
    #include <string>
    using namespace std;
    
    class bruch
    {
    public:
    
    	int zaehler;
    	int nenner;
    	int zaehler2;
    	int nenner2;
    	int erg_zaehler;
    	int erg_nenner;
    
    	bruch(); //Konstruktor
    
    	void einlesen();
    	void einlesen2();
    	void multiplizieren(int zx, int nx);
    	void dividieren(int zx, int nx);
    	void addieren(int zx, int nx);
    	void subtrahieren(int zx, int nx);
    	void ausgeben();
    };
    
    bruch::bruch()
    {
    	zaehler     = 0;
    	nenner      = 0;
    	zaehler2    = 0;
    	nenner2     = 0;
    	erg_zaehler = 0;
    	erg_nenner  = 0;
    }
    
    void bruch::einlesen()
    {
    	cout << "\nGeben Sie den 1.Bruch ein: ";
    	string eingabe;
    	cin  >> eingabe;
    
    	//Anzahl der Zeichen
    	int anz = 0;
    	for(int i=0; i<eingabe.size(); i++)
    	{
    		anz++;
    	}
    
    	//Position des Bruchstrichs suchen
    	int    pos        = eingabe.find("/", 0);
    	string strZaehler = eingabe.substr(0,pos);
    	string strNenner  = eingabe.substr(pos+1, anz);
    
    	//string in int umwandeln
    	zaehler = atoi(strZaehler.c_str());
    	nenner  = atoi(strNenner.c_str());
    }
    
    void bruch::einlesen2()
    {
    	cout << "\nGeben Sie den 2.Bruch ein: ";
    	string eingabe;
    	cin  >> eingabe;
    
    	int anz = 0;
    	for(int i=0; i<eingabe.size(); i++)
    	{
    		anz++;
    	}
    
    	int    pos         = eingabe.find("/", 0);
    	string strZaehler2 = eingabe.substr(0,pos);
    	string strNenner2  = eingabe.substr(pos+1, anz);
    
    	zaehler2 = atoi(strZaehler2.c_str());
    	nenner2  = atoi(strNenner2.c_str());
    }
    
    void bruch::multiplizieren(int zx, int nx)
    {
    	erg_zaehler = zaehler * zx;
    	erg_nenner  = nenner  * nx;
    }
    
    void bruch::dividieren(int zx, int nx)
    {
    	erg_zaehler = zaehler * nx;
    	erg_nenner  = nenner * zx;
    }
    
    void bruch::addieren(int zx, int nx)
    {
    	if(nenner == nenner2) //wenn gleicher Nenner
    	{
    		erg_zaehler = zaehler + zx;
    		erg_nenner  = nenner;
    	}
    	else //wenn ungleicher Nenner
    	{
    		erg_nenner  = nenner*nenner2;
    		erg_zaehler = (zaehler*nenner2) + (zaehler2*nenner);
    	}
    }
    
    void bruch::subtrahieren(int zx, int nx)
    {
    	if(nenner == nenner2)
    	{
    		erg_zaehler = zaehler - zx;
    		erg_nenner  = nenner;
    	}
    	else
    	{
    		erg_nenner = nenner*nenner2;
    		erg_zaehler = (zaehler*nenner2) - (zaehler2*nenner);
    	}
    }
    
    void bruch::ausgeben()
    {
    	int i = 0;
    	int rest[100];
    	rest[0] = erg_nenner;
    	rest[1] = erg_zaehler;
    
    	if(erg_zaehler <= erg_nenner)
    	{
    	for(i=0; i<=100; i++) //kürzen
    		{
    			rest[i+2] = rest[i]%rest[i+1];
    
    			if(rest[i+2] == 0)
    			{
    				erg_zaehler /= rest[i+1];
    				erg_nenner  /= rest[i+1];
    				break;
    			}
    		}
    		cout << "\nErgebnis: " << erg_zaehler << "/" << erg_nenner << endl;
    	}
    
    	else
    	{
    		// umwandeln
    		int zahl    = erg_zaehler/erg_nenner; 
    		int misch   = erg_zaehler-(zahl*erg_nenner); 
    		erg_zaehler = misch;
    
    		for(i=0; i<=100; i++) //kürzen
    		{
    			rest[i+2] = rest[i]%rest[i+1];
    
    			if(rest[i+2] == 0)
    			{
    				erg_zaehler /= rest[i+1];
    				erg_nenner  /= rest[i+1];
    				break;
    			}
    		}
    		cout << "\nErgebnis: " << zahl << " " << erg_zaehler << "/" << erg_nenner << endl;
    	}
    }
    
    int main()
    {
    	cout << "----------------------------" << endl;
    	cout << "Brueche addieren         <1>" << endl;
    	cout << "Brueche subtrahieren     <2>" << endl;
    	cout << "Brueche multiplizieren   <3>" << endl;
    	cout << "Brueche dividieren       <4>" << endl;
    
    	while(true)
    		{
    		cout << "\nBitte waehlen Sie einen Menuepunkt: ";
    		int wahl;
    		cin  >> wahl;
    
    		bruch b;
    		switch(wahl)
    		{
    		case 1: b.einlesen();
    				b.einlesen2();
    				b.addieren(b.zaehler2, b.nenner2);
    				b.ausgeben();
    				break;
    		case 2: b.einlesen();
    				b.einlesen2();
    				b.subtrahieren(b.zaehler2, b.nenner2);
    				b.ausgeben();
    				break;
    		case 3: b.einlesen();
    				b.einlesen2();
    				b.multiplizieren(b.zaehler2, b.nenner2);
    				b.ausgeben();
    				break;
    		case 4: b.einlesen();
    				b.einlesen2();
    				b.dividieren(b.zaehler2, b.nenner2);
    				b.ausgeben();
    				break;
    		default: cout << "Ungueltige Eingabe!" << endl;
    		}
    
    		cout << "\nMoechten Sie noch eine Rechnung durchfuehren?(y/n) ";
    		string neu;
    		cin  >> neu;
    		if(neu == "y")
    			continue;
    		else
    		{
    			cout << "Das Programm wird beendent!" << endl;
    			break;
    		}
    	}
    
    	return 0;
    }
    

    PS: die "Menüstruktur" ist erst mal ein Mittel zum Zweck... 😉



  • Kritikpunkte:

    1. public-Variablen. Ein Bruch hat eine Invariante (eine Bedingung die für ein gültiges Objekt immer zutreffen muss), nämlich dass der Nenner nicht 0 ist. Ich würde auch noch hinzufügen, dass der Bruch immer gekürzt ist. Wenn du mit public-Variablen den direkten Zugriff auf Zähler und Nenner zuläßt, kannst du die Invariante nicht aufrechterhalten.
      Schlussfolgerung: Zaehler und Nenner sollten private sein ("Kapselung")
    2. zuviele Variablen. Ein Bruch hat *einen* Zähler und *einen* Nenner, nicht jeweils 3.
    3. einlesen und ausgeben gehören nicht in die Klasse
    4. hier bietet sich Operatorüberladung an, aber es kann natürlich gut sein, dass du dich damit noch nicht beschäftigt hast.

    wenn man das beherzigt, sieht die Klasse ungefähr so aus:

    class Bruch
    {
      int zaehler, nenner;
      void kuerzen() // reicht als private Funktion
      {
        /* Euklids Algorithmus zur GGT-Berechnung anwenden, Zähler und Nenner durch ggt teilen */
      }
    public:
      Bruch(int z = 0, int n = 1)
      {
        assert(n != 0); // oder eine andere Art den Fehler abzufangen
        kuerzen();
      }
      int get_zaehler() { return zaehler; }
      int get_nenner() { return nenner; }
      /* Keine set-Methoden */
    
      Bruch& addieren(const Bruch& other)
      {
        /* additionsalgorithmus implementieren */
        kuerzen();
        return *this;
      }
      // ...
    };
    
    void einlesen(Bruch& br)
    {
      /* wie bei dir ... */
      br = Bruch(z, n);
    }
    
    void ausgeben(Bruch const& br)
    {
      cout << br.get_zaehler() << '/' << br.get_nenner();
    }
    
    Bruch addiere(Bruch const& br1, Bruch const& br2)
    {
      Bruch ergebnis = br1;
      ergebnis.addieren(br2);
      return ergebnis;
    }
    // ...
    int main()
    {
      Bruch a(2, 3), b(20, 2);
      Bruch c = addiere(a, b);
      ausgeben(c);
    }
    

    Das ist schon gut, aber nocht nicht ganz das gelbe vom Ei. Dafür würde man aber Operatorüberladung brauchen. Ich deute mal an, wie das dann aussehen würde ...

    Bruch operator+(Bruch const& a, Bruch const& b)
    {
      Bruch ergebnis = a;
      ergebnis += b;
      return ergebnis;
    }
    

    (du stellst fest, dass ich im Prinzip einfach die Memberfunktion addieren zum operator+= und die freie Funktion addiere zum operator+ umfunktioniert habe)
    Ähnliches macht man für den operator<< um Brüche genauso wie alles andere ausgeben zu können, also cout << c << endl; statt ausgeben(c).



  • Kritikpunkte:

    1. public-Variablen. Ein Bruch hat eine Invariante (eine Bedingung die für ein gültiges Objekt immer zutreffen muss), nämlich dass der Nenner nicht 0 ist. Ich würde auch noch hinzufügen, dass der Bruch immer gekürzt ist. Wenn du mit public-Variablen den direkten Zugriff auf Zähler und Nenner zuläßt, kannst du die Invariante nicht aufrechterhalten.
      Schlussfolgerung: Zaehler und Nenner sollten private sein ("Kapselung")

    Ja, das stimmt. Ich hatte auch versucht, mit Hilfe einer get-Methode zu arbeiten, aber habs irgendwie nicht hinbekommen, darum hab ich den direkten Zugriff erst mal gelassen.

    1. zuviele Variablen. Ein Bruch hat *einen* Zähler und *einen* Nenner, nicht jeweils 3.

    Stimmt natürlich auch. Aber irgendwie dachte ich, ich bräuchte für die Rechnungen drei Brüche, daher kommen wohl die ganzen Variablen.

    1. einlesen und ausgeben gehören nicht in die Klasse

    hm, kann man sie denn nicht als Methoden ansehen? 😕

    1. hier bietet sich Operatorüberladung an, aber es kann natürlich gut sein, dass du dich damit noch nicht beschäftigt hast.

    Damit hast du Recht, aber ich kanns mir ja mal als Nächstes anschauen... 🙂

    Ich werd mir jetzt in aller Ruhe mal deinen Vorschlag ansehen und versuchen ihn nachzuvollziehen... 🙄 Vielen Dank schon mal!



  • Ich glaueb, du hast da was falsch verstanden...

    Bei einer Klasse geht es immer um ein Objekt, also auch ein Bruch. Wenn deine Klasse Bruch heißt, dann stellt sie EINEN Bruch dar, und nicht wie du es erst gemacht hast, 2 STück, die man dann addieren kann usw. Dies macht man dann so wie Bashar, Für jeden Bruch ein eigenes Objekt erstellen und die Brüche dann mit Memberfunktionen des einen Bruches addieren, und den anderen als Argument übergeben.

    Gruß, Maxi



  • Bei einer Klasse geht es immer um ein Objekt, also auch ein Bruch. Wenn deine Klasse Bruch heißt, dann stellt sie EINEN Bruch dar, und nicht wie du es erst gemacht hast, 2 STück, die man dann addieren kann usw. Für jeden Bruch ein eigenes Objekt erstellen und die Brüche dann mit Memberfunktionen des einen Bruches addieren, und den anderen als Argument übergeben.

    Ich sag ja, mit den Klassen steh ich noch auf Kriegsfuß, aber langsam hab ichs glaub ich einigermaßen kapiert, aber: was ist denn eine Member-Funktion? Da versagt mein Wissen... 🙄



  • Eine Memberfunktion ist eine Funktion, die innerhalb einer Klasse deklariert ist. 🙂


Anmelden zum Antworten