C++ / Oop

FeriX

Cadet 2nd Year
Registriert
Aug. 2005
Beiträge
23
Hi,

ich habe in der FH vor kurzem mit OOP in C++ angefangen und habe da nun eine kleine Verständnisfrage. Angenommen wir haben eine Klasse "Bruch" und in der Implementierungsdatei eine Funktion namens "Addiere" ebenfalls vom Typ Bruch.
Nun folgende 2 Versionen, um die Funktion zu deklariern.

1. Bruch Bruch::Addiere (Bruch a) {return...}
2. Bruch Bruch::Addiere (Const Bruch &a) {return...}

Aufgerufen wird die Funktion dann aus dem Hauptprogramm mit z.B.

Bruch1.Addiere(Bruch2)

Mich würde jetzt einfach interessieren wo bei diesen 2 Versionen ein Unterschied besteht bei der Übergabe und welcher???

Also im Prinzip der Unterschied zwischen "(Bruch a)" und (Const Bruch &a) beim Funktionsaufruf und der Parameterübergabe!?!?

Würde mich echt glücklich machen, wenn mich da jemand aufklären könnte :)
 
Sage ich nur dazu

////////////////////////////////////////

Code:
#include <iostream>
using namespace std;

struct Bruch
   {
   int zaehler, nenner;
   };
   
Bruch addiere(Bruch b1, Bruch b2)
   {
    Bruch br = {(b1.zaehler + b2.zaehler),
            (b1.nenner + b2.nenner)
                };
   return br;
   }
   
ostream& operator << (ostream co, Bruch br)
   {
   co << br.zaehler << ", " << br.nenner;
   return co;
   }
   
int main()
   {
   cout << addiere(Bruch(2,3), Bruch(4,3)) << "\n";
   
   system("pause");
   return 0;
   }
 
Zuletzt bearbeitet von einem Moderator:
Oder dies Hier

VVVVVVVVVVVVVVVVVVVVV


Code:
#include "bruch.h"

void bruch::korrektur()
{
  	if (nenner < 0) nenner=nenner*-1, zaehler = zaehler*-1;	
}  	
    
bruch::bruch()
{
	zaehler = 0;
	nenner = 1;    
}

bruch::bruch(int z, int n)
{
    zaehler = z;
    nenner = n;
}    

void bruch::eingabe()
{
    char c;
    cin >> zaehler >> c >> nenner; 
}    

void bruch::ausgabe()
{
	korrektur();
 	cout << zaehler;
    if (nenner != 1)
    cout << "/" << nenner;  
}   

bruch 	bruch::addiere(bruch b2)const
{
//    bruch erg;
//    erg.nenner = b2.nenner * nenner; // oder this->nenner
//    erg.zaehler = b2.zaehler * nenner + zaehler * b2.nenner;
    return bruch(b2.zaehler * nenner + zaehler * b2.nenner, b2.nenner * nenner);
}   

bruch 	bruch::multi(bruch b2)const
{
    return bruch(b2.zaehler * zaehler, b2.nenner * nenner);
}

bruch 	bruch::gekuerzt() const
{
    int g=ggt(zaehler, nenner);

//    bruch erg;
//    erg.nenner = nenner/g;
//    erg.zaehler = zaehler/g;
//    return erg;
	return bruch(zaehler/g, nenner/g);   
}    

bruch bruch::operator-() const
{
    return bruch(-zaehler, nenner);
}

bruch bruch::operator~() const
{
    return bruch(nenner, zaehler);
}    
    
int ggt(int z1, int z2)
{
	int teiler;
    for(teiler=z1 ; teiler >=2 ; teiler--)
    	if (z1 % teiler == 0 && z2 % teiler ==0)
     		return teiler;
	return 1;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++
//bruch operator+(bruch b1, bruch b2)                   //call by value
bruch operator+(const bruch& b1, const bruch& b2)       //call by reference
{
    return b1.addiere(b2);
}

//--------------------------------------------------
bruch operator-(const bruch& b1, const bruch& b2)       
{
    return b1.addiere(-b2);
}

//****************************************************
bruch operator*(const bruch& b1, const bruch& b2)       
{
    return b1.multi(b2);
}

//////////////////////////////////////////////////////
bruch operator/(const bruch& b1, const bruch& b2)       
{
    return b1.multi(~b2);
} 


ostream& operator<<(ostream &os, const bruch& b)    //macht die Fkt. Ausgabe unnötig
{
    os << b.zaehler;
    if(b.nenner != 1)
        os << "/" << b.nenner; 
    return os;
}    


istream& operator>>(istream &is, bruch& b)    //macht die Fkt. Eingabe unnötig
{
    char c;
    is >> b.zaehler >> c >> b.nenner; 
    return is;
}
 
Zuletzt bearbeitet von einem Moderator:
FeriX schrieb:
Würde mich echt glücklich machen, wenn mich da jemand aufklären könnte :)
Na dann mache ich dich doch mal glücklich ;)

Das Erste ist eine Übergabe per Value und das Zweite per (const-)Referenz.

In erster Variante wird das Objekt (also der Bruch) über den Kopierkonstruktor kopiert und an die Funktion übergeben. Das macht eines offensichtlich - ist es ein "teuer" zu kopierendes Objekt (sprich, das Kopieren ist nicht trivial und braucht "viel" Zeit/Speicher/etc), so ist das ungünstig. Es gibt auch Klassen, die keinen Kopierkonstruktor bereitstellen. Dann funktioniert Übergabe per Value nicht.

In zweiter Variante wird eine konstante Referenz auf das Originalobjekt erzeugt. Für das ursprüngliche Objekt wird ein "Alias" oder "zusätzlicher Name" angelegt (genannt Referenz), der genau das selbe Objekt bezeichnet. Dieser wird an die Funktion übergeben. Jeder Zugriff auf diesen Alias verhält sich so, als würde man mit dem Originalobjekt arbeiten (das schließt Zuweisungen ein - eine Referenz lässt sich also nie zuweisen, sondern nur initialisieren). Hier wird also keine Kopie erstellt. Nun kommen wir zum const - würde dort kein const stehen und du würdest das über die Referenz übergebene Objekt in der Funktion ändern, so ändert sich das Originalobjekt außerhalb der Funktion! Es wurde ja schließlich keine Kopie erzeugt. Das Wort const sorgt dafür, dass auf dem Übergebenen Objekt nur Methoden aufgerufen werden können, die "hintendran" das Wörtchen const zu stehen haben. const-Methoden ist es untersagt, Membervariablen zu ändern.
Durch das const wird also sichergestellt, dass das ursprüngliche Objekt nicht geändert wird. Es ist somit auch eine Art "Garantie für den Aufrufer".

(Von schmutzigen Techniken wie "wegcasten des const" sehe ich mal ab... =) Für den Moment sollte das alles so reichen :))

Eddit: Mit OOP hat das jetzt im übrigen eigentlich nichts zu tun :)
 
Zuletzt bearbeitet:
Vielen Dank mit der Antwort hast mich eigentlich schon fast komplett glücklich gemacht ;)

Aber hab da jetzt noch 1-2 Folgefragen...

"Das Wort const sorgt dafür, dass auf dem Übergebenen Objekt nur Methoden aufgerufen werden können, die "hintendran" das Wörtchen const zu stehen haben. const-Methoden ist es untersagt, Membervariablen zu ändern"

Meinst du damit, dass in dieser Funktion nur Funktionen auf dieses Objekt aufgerufen werden können die const sind?
Also wenn man den bruch erst noch kürzen will bevor man addiert dann z.B. der Aufruf in Addiere: a.kuerze
Muss dann die Funktion kuerze ebenso const sein????

Desweiteren nochwas...

Bruch Bruch::Addiere (Bruch a) const{return...}

Welche Bedeutung hat es dieses const nochmal hintendranzusetzen?
 
das sind diese bereits erwähnten const methoden. diese methoden dürfen das object zu dem sie gehören nicht ändern, ansonsten bekommst du einen fehler vom compiler. du darfst in einer solchen methode also keine member variablen verändern oder andere nicht const methoden deines bruch-objects aufrufen(da diese ja ansonsten das object verändern könnten, und damit deine methode der ausgang dieser veränderung wäre).

allerdings ist dein beispiel vielleicht etwas verwirrend, denn mit dem bruch-object a darfst du machen was du willst, nur this darf eben in dieser methode nicht geändert werden.

damit hätten wir deine andere frage ja auch schon beantwortet: a.kuerze muss natürlich const sein, wenn sie von einer anderen const methode der selben klasse aufrufbar sein soll.
 
Siberian..Husky schrieb:
a.kuerze muss natürlich const sein, wenn sie von einer anderen const methode der selben klasse aufrufbar sein soll.
Nein, das ist nicht richtig. Bruch::kuerze verändert ja Membervariablen. Trotzdem kann man es einfach so implementieren. Beispiel:

Code:
class Bruch
{
private:
   /** es sollte nie notwendig sein, das von aussen aufzurufen */
   void kuerze();
public:
   Bruch& operator+=( const Bruch&);
   const Bruch operator+( const Bruch&) const;
};

Bruch& Bruch::operator+=( const Bruch& other)
{
   zaehler = zaehler * other.nenner + other.zaehler * nenner;
   nenner = nenner * other.nenner;
   kuerze();
   return *this;
}

const Bruch Bruch::operator+( const Bruch& other) const
{
   /** result hier ist nicht const. von result duerfen also auch
         nicht-const Methoden aufgerufen werden (+= und kuerze) */
   Bruch result( *this);
   result += other;
   return result;
}

void Bruch::kuerze()
{
   .... kuerzen
}

Achso, das Beispiel zeigt auch gleich etwas anderes: Man implementiert + intern immer mit dem += Operator. Das ist wichtig für konsistentes Verhalten.

(Auch wenn du Operator-Überladung noch nicht kennen solltest, ist das hoffentlich so intuitiv verständlich :))
 
Siberian..Husky schrieb:
d
allerdings ist dein beispiel vielleicht etwas verwirrend, denn mit dem bruch-object a darfst du machen was du willst, nur this darf eben in dieser methode nicht geändert werden.

Was willst du damit sagen?

Dass es Bruch a quasi nur in dieser Methode const ist und nicht verändert werden darf???




Bei dem Beispiel versteh ich leider nicht mehr viel :(
 
7H3 N4C3R schrieb:
Nein, das ist nicht richtig. Bruch::kuerze verändert ja Membervariablen. ...

ist es wohl :P. ob Bruch::kuerze nun das objekt verändert hängt ja wohl von seiner implementierung ab. die frage war ob kuerze const sein muss, wenn die addieren methode(die const ist) die kuerze-methode aufrufen will.

in der tat wäre das nicht besonders sinnvoll, schließlich wird es wohl problematisch sein eine kuerze methode zu implementieren die funktioniert, aber das objekt nicht verändert... sinnvoll wäre es in diesem beispiel also wenn, wie du sagst, die addieren methode nicht const wäre.


FeriX schrieb:
Was willst du damit sagen?

Dass es Bruch a quasi nur in dieser Methode const ist und nicht verändert werden darf???




Bei dem Beispiel versteh ich leider nicht mehr viel :(

das hab ich mir fast gedacht ;).

das verständniss-problem kommt daher das wir 2 verschiedene bruch objekte haben(im grunde haben wir sogar 3, aber den return wert lassen wir einfach mal aussen vor, um nicht noch mehr verwirrung zu stiften...). dafür nochmal dein beispiel:
Bruch Bruch::Addiere (Bruch a) const
{.....}
da haben wir einmal Bruch a, und ausserdem natürlich das object auf das Addiere aufgerufen worden ist, also this. in diesem beispiel darfst du mit Bruch a tun was du willst. Du darfst nur nicht den Bruch this verändern, also das Bruch objekt auf das die methode Addiere aufgerufen wurde. das bedeutet du dürftest auch kein this->kuerze aufrufen, wenn kuerze nicht const wäre. du dürftest allerdings a.kuerze aufrufen, ob die methoden nun const ist oder nicht.

const methoden dürfen das objekt zu dem sie gehören nicht ändern. sie dürfen also auch keine anderen nicht const methoden auf dieses objekt aufrufen. eine const methode darf allerdings alle anderen variablen die nicht const sind weiterhin verändern. mit übergebenen parametern oder globale variablen die nicht const sind darfst du also machen was du willst.


ich hoffe mal das ich jetzt nicht selbst irgendwo durcheinander gekommen bin. nächstesmal suchst du dir besser ein beispiel mit unterschiedlichen typen aus :P.
 
Zuletzt bearbeitet:
Hey super vielen Dank für die Antworten hab das jetzt gecheggt...allerdings mit der anderen Version:

Bruch Bruch::Addiere (const Bruch &a) const

...darf ich dann auch nicht mehr bruch a beliebig verändern ne!? ;)

Ich hoff das is jetzt so sonst zweifel ich noch an mir selbst :D
 
Zurück
Oben