Eigene Klasse Vektor



  • Hallo!

    Muss eine eigene Implementierung für einen Vektor schreiben:

    #include <iostream>
    
    using namespace std;
    
    class Vektor
    {
    	private:
    		const int dim;
    		int *komponenten;
    
    	public:
    		Vektor(int d) : dim(d) { komponenten = new int[dim]; }
    		Vektor(const Vektor&);
    		~Vektor();
    		int getDim() { return dim; }
    		int getVek(int);
    		void setVek(int, int);
    		Vektor& operator+=(const Vektor&);
    		ostream& operator<<(ostream&, const Vektor&);
    };
    
    Vektor::Vektor(const Vektor& other)
    {
    	for(int i=0; i<getDim(); i++)
    		setVek(i, other.getVek(i));
    }
    
    Vektor::~Vektor()
    {
    	delete komponenten;
    }
    
    int Vektor::getVek(int pos)
    {
    	return komponenten[pos];
    }
    
    void Vektor::setVek(int pos, int wert)
    {
    	komponenten[pos]=wert;
    }
    
    Vektor& Vektor::operator+=(const Vektor& other)
    {
    	for(int i=0; i<getDim(); i++)
    		setVek(i, (getVek(i)+other.getVek(i)));
    
    	return (*this);
    }
    
    ostream& Vektor::operator<<(ostream& ausgabe, const Vektor& other)
    {
    	ausgabe << "Vektor mit " << getDim() << " Komponenten:" << endl;
    	for(int i=0; i<getDim(); i++)
    		ausgabe << i << ". Komponente: " << getVek(i) << endl;
    
    	return ausgabe;
    }
    

    Probleme habe ich bei der Operatorüberladung und dem Kopierkonstruktor. Kann mir jemand vielleicht sagen, wo meine Fehler liegen?

    Danke!
    Tobias



  • Hallo Tobias,

    stream-Operatoren müssen als friend-Funktionen implementiert werden. Das heisst sie können nicht Methoden der Klasse Vector sein!
    Der Grund dafür:

    cout << vec;   /* wird intern zu ==> */   cout.operator<<( cout, vec );
    // bzw.
    cout << vec << endl;   /* zu ==> */   endl.operator<<( cout.operator<<( cout, vec ) );
    

    schaut gruslig, ist aber so. endl und cout sind vom Typ ostream und stehen durch diese interne "Umstellung" immer auf der linken Seite. Bei istreams (cin) ist das genauso. Das heisst: Die Operatoren << und >> "gehören" den stream-Klassen, und man kann die Teile nur als Funktion überladen.
    Versuchs mal so:

    Klassendeklaration:

    friend ostream& operator<<( ostream &stream, const Vector &v );
    //... und die Implementierung ...
    ostream &operator<<( ostream &stream, const Vector &v ) { ... }
    

    Kopierkonstruktor:
    getDim() ist in deiner for-Schleife praktisch: this->getDim() - liefert die also den noch nicht initialisierten Wert der zu erstellenden Instanz.
    Das Folgende sollte das Gewünschte tun...

    Vector::Vector( const Vector &other ) : dim( other.dim )
    {
       komponenten = new int[dim]
       for( int i=0; i<dim; ++i )
          komponenten[i] = other.komponenten[i];
    }
    

    Da du ja in der eigenen Klasse bist - auch im Zugriff auf andere Instanzen!!! - kannst direkt drauf zugreifen und musst nicht immer die "getter"-Methoden aufrufen.

    Destruktor:
    Da du mit new eine Reihung anforderst, solltest die so freigeben:

    delete[] komponenten;
    

    hoffe des passt soweit ...
    Grüssle,
    Brannigan



  • stream-Operatoren müssen als friend-Funktionen implementiert werden.

    Wer erzählt eigentlich immer solche Sachen? Will man die hübsche cout << obj; Syntax, dann muss man natürlich den op<< als *freie* Funktion implementieren. Einen friend-Zwang gibt es aber freilich nicht.
    Besser ist es den op<< (bzw. op>>) hübsch über die öffentliche Schnittstelle der entsprechenden Klasse zu implementieren.
    Das hat drei entscheidene Vorteile (mehr fallen mir gerade nicht ein):
    1. Keine unnötigen Freunde -> keine unnötigen Abhängigkeiten -> kein unnötiger Ärger.
    2. polymorphe Ein-/Ausgabe möglich
    3. Kein Ärger mit komplizierten Template-Friend-Deklarationen.

    Es ist wie im echten Leben. Freunde machen nur Ärger und kosten Geld. Hm, ok die "Echte-Leben-"-Analogie passt hier nicht so gut. In der Softwareentwicklung sollte man aber schüchterne, introvertierte Objekte bevorzugen.
    Also nicht wahllos irgendwelche Hippies zu Freunden erklären.



  • Hallo,
    wo ich den Code gerade mal so überfliege, möchte ich doch noch mal an die Regel der Großen Drei erinnern. Hier passt sie mal wieder sowas von.



  • Hi,
    bei der Sache mit den friends muss ich dir Recht geben! Das "müssen" ist natürlich relativ! 😉
    Aber Die Regel der Großen Drei trifft hier wohl nicht zu: Ne flache Kopie tuts hier ja nicht!
    Ausserdem: Ich halte das hier für ein akademisches Beispiel zum Lernen von OOP mit C++ ... da soll man doch noch schön alles implementieren 😉



  • Sepp Brannigan schrieb:

    Aber Die Regel der Großen Drei trifft hier wohl nicht zu: Ne flache Kopie tuts hier ja nicht!

    genau, und weil es eine flache kopie nicht tut, greift die Regel der grossen Drei!

    denn was sagt diese regel uns:

    Wenn du tust Ctor, CopyCtor oder op= implementierst, wirst du auch die anderen beiden brauchen



  • oops ... mein Fehler ... das ist ein +=. Übersehen!
    Drum dacht ich, du meinst: Man könnte sich den ganzen Zirkus lt. den grossen drei sparen. Sorry
    Dann braucht man sicherlich das volle Programm. Aber hier fehlen sowiso noch ein paar Sachen, wie Tobias vielleicht noch feststellen wird. z.B. ein explicit vorm Ctor oder so ...



  • Hi!
    Danke für die Anregungen!

    Hab's mal umgesetzt:

    #include <iostream>
    
    using namespace std;
    
    class Vektor
    {
    		friend ostream &operator<<(ostream&, const Vektor&);
    
    	private:
    		const int dim;
    		int *komponenten;
    
    	public:
    		Vektor(int d) : dim(d) { komponenten = new int[dim]; }
    		Vektor(const Vektor&);
    		~Vektor();
    		int getDim() { return dim; }
    		int getVek(int);
    		void setVek(int, int);
    		Vektor &operator+=(const Vektor&);
    };
    
    Vektor::Vektor(const Vektor& other) : dim(other.dim)
    {
    	komponenten = new int[dim];
    	for(int i=0; i<dim; i++)
    		komponenten[i]=other.komponenten[i];
    }
    
    Vektor::~Vektor()
    {
    	delete[] komponenten;
    }
    
    int Vektor::getVek(int pos)
    {
    	return komponenten[pos];
    }
    
    void Vektor::setVek(int pos, int wert)
    {
    	komponenten[pos]=wert;
    }
    
    Vektor &Vektor::operator+=(const Vektor& other)
    {
    	for(int i=0; i<getDim(); i++)
    		komponenten[i]+=other.komponenten[i];
    
    	return (*this);
    }
    
    ostream &operator<<(ostream& ausgabe, const Vektor& other)
    {
    	ausgabe << "Vektor mit " << other.getDim() << " Komponenten:" << endl;
    	for(int i=0; i<other.getDim(); i++)
    		ausgabe << i << ". Komponente: " << other.getVek(i) << endl;
    
    	return ausgabe;
    }
    

    Laut Compiler macht nur noch die Ausgabe-Überladung Probleme. Vor allem die benutzten Methoden, aber warum?

    THX!



  • Hallo,

    sorry ... daran hat ich nicht gedacht.
    Da du in der Ausgabefunktion eine const Vector Referenz übernimmst, ist das Teil in der Funktion konstant und erlaubt dir nicht auf normale Methoden zuzugreifen - denn die könnten ja das Teil unbemerkt verändern. Deshalb wird gemeckert.
    Ausweg: Du musst die entsprechenden Methoden für const-Objekte freigeben. Sowas macht man per const-Angabe hinter den Parameterklammern:

    int getDim() const { return dim; } // würd ich übrigens inline machen
    // und
    int getVek(int) const;  // Deklaration
    int Vector::getVek(int pos) const { ... }
    

    das wars. Übrigens: Der Compiler prüft die const-Funktionen auf Schummeleien 😉

    viel Erfolg!



  • Ich bin über das Thema nur drübergeflogen, aber mir ist was vielleicht Wichtiges eingefallen... falls Du MS Visual C++ 6.0 verewendest gibt's da möglicherweise Probleme beim Überladen des stream-Operators (da bin ich nämlich mal fast dran verzweifelt).
    Du solltest in dem Fall dann mal den neusten Service Pack für's Visual Studion installieren (steht aber glaube ich in den FAQs).



  • Sepp Brannigan schrieb:

    int getDim() const { return dim; } // würd ich übrigens inline machen
    

    Ist es so auch schon.



  • operator void schrieb:

    Ist es so auch schon.

    echt? Bitte mehr Info! *lechz*



  • Sepp Brannigan schrieb:

    operator void schrieb:

    Ist es so auch schon.

    echt? Bitte mehr Info! *lechz*

    wird die funktion innerhalb der klasse definiert, ist sie automatisch inline.



  • aha ... ist mir wohl entgangen.



  • Danke für die vielen Tipps! 🙂

    Mal ne andere Frage:
    Wenn ich in einer Klasse einen Zeiger defiere und diesen per Konstruktor einem
    konstanten Zeiger zuweisen will, wie mache ich das?

    class Flugzeug
    {
    	private:
    		char *typ;
    		...
    	public:
    		Flugzeug(const char *t) : typ(t) {}
                      ...
    };
    

    So klappts nicht. Wie denn?

    THX
    Tobias



  • Du bekommst nen Zeiger auf konstanten Inhalt, weist den einem anderen - nicht const - zu und schon kannst nach Herzenslust rumschreiben... wär doch frech!
    Also entweder den Zeiger *char typ; als *const char typ; deklarieren, oder das const aus der Parameterliste des Konstruktors rausschmeissen. Das wär aber unpraktisch, da man dann keine Zeichenketten mehr direkt übergeben kann - also: Flugzeug("Boing 747"); würd dann nicht mehr gehen.

    Ausserdem: Hier ist nicht der Zeiger konstant, sondern der Inhalt.

    const char *const abc = "abc";
    

    Das const vor dem char bezieht sich auf den Inhalt. Das andere auf den Zeiger selbst. Ist ein feiner Unterschied.
    Aber wer die Wahl hat ... braucht für den Spott nicht zu sorgen - oder wie war das?



  • THX!

    Aber wenn ich char *typ nun auch const mache, speichere ich dann nicht nur den ersten Buchstaben des übergebenen Namens?

    OK -> Hat sich erledigt. My fault!

    Ciao & nochmals Danke!



  • Alles was du mit z.B. der Variable typ speichern kannst ist die Adresse eines Zeichens irgendwo im Speicher - mehr nicht! Möglicherweise ist dieses Zeichen das erste einer null-terminierten Zeichenkette. Falls du daran denkst eine Zeichenkette in einer Funktion zu übernehmen und dann typ zuzuweisen: Du weist nur die Adresse zu! Ich vermute mal, du willst die übergebene Zeichenkette in der Klasse ablegen - also kopieren. Da wir im C++-Teil dieses Forums sind: Was spricht gegen die Benutzung der Klasse String?



  • Sepp Brannigan schrieb:

    cout << vec;   /* wird intern zu ==> */   cout.operator<<( cout, vec );
    // bzw.
    cout << vec << endl;   /* zu ==> */   endl.operator<<( cout.operator<<( cout, vec ) );
    

    😕



  • PeterTheMaster schrieb:

    😕

    Antwort: 42


Anmelden zum Antworten