<< operator für 2dimensionales Array überladen



  • Hallo an alle,
    ich würde gerne den "<<" Operator so überladen, dass ich ein zweidimensionales Array auf folgende Art ausgeben kann:

    char my_array[10][10];
    
    // hier wird array mit irgendwas befüllt
    
    cout << my_array;
    

    Das Array könnte auch in einer Klasse verpackt sein, sodass man dann die Ausgabe via

    cout << klasse;
    

    macht.

    Hoffe es kann mir jemand helfen. Ich habe schon gegoogelt, dabei aber kein passendes Beispiel gefunden. Auch in Büchern habe ich schon nachgeschlagen, hat aber nichts genutzt.

    Danke im voraus für jede Antwort!

    mfg Trust



  • Du musst sogar das Array in eine Klasse "packen". Denn Du kannst Operatoren nicht überladen, wenn nicht mindestens ein benutzerdefinierter Typ für Parameter involviert ist.

    Das Stichwort für Deine Internetsuche ist "operator overloading c++".

    Viel Glück,
    SP



  • Danke, danach hatte ich wie gesagt bereits gesucht. Ich hab es ja auch schon probiert:

    Die Klasse sieht ungefähr so aus:

    class My_Array
    {
      public:
        friend std::ostream &operator<<(std::ostream&, const Board&);
        virtual void toStream(std::ostream&) const;
        void drawBoard();
        ...
      private:
        int width_;
        int height_;
        char** board_;
        Board(const Board&);
    };
    
    void Board::drawBoard()
    {
      std::cout << "Using funktion drawBoard()." << std::endl;
      std::cout << board_;
    }
    
    std::ostream &operator<<(std::ostream &os, const Board &board)
    {
      os << "Using overloaded << operator!" << std::endl;
      board.toStream(os);
      return os;
    }
    
    void Board::toStream(std::ostream& os) const
    {
      os << "Using function toStream()." << std::endl;
      for(int iter_y=0;iter_y<height_;iter_y++)
      {
        for(int iter_x=0;iter_x<width_;iter_x++)
        {
          os << board_[iter_x][iter_y];
        }
        os << std::endl;
      }
    }
    

    Wobei die beiden Ausgaben dazu dienen zu sehen wann der Operator verwendet wurde. Funktioniert aber alles nicht so gut. Die ausgabe die ich erhalte wenn ich ein Array mit 20*20 Feldern ausgeben will ist einfach nur:

    Betrete Funktion drawBoard().
    0x948e030
    

    Also er kommt zwar in die Funktion drawBoard und damit wird der überladene "<<" Operator auch sicher verwendet, aber die Ausagbe ist einfach nur eine Adresse.
    Hoffe es ist kein allzu peinlicher Fehler und hoffe, dass man mir durch den Code besser helfen kann.

    mfg Trust



  • Da hast du aber einiges durcheinander gebracht...

    Du gibt mit 'cout << board' einfach die Anfangsadresse deines char-Arrays aus (in hexadezimaler Notation).

    Du mußt dir selber einfach mal einig sein, was nun die Klassen 'My_Array' und 'Board' sein sollen. M.E. reicht eine einzige Klasse (für die Kapselung des internen Arrays) aus. (oder hast du dich einfach hier verschrieben?)

    Hier die Lösung:

    void Board::drawBoard()
    {
      std::cout << "Using funktion drawBoard()." << std::endl;
      std::cout << *this; // ruft operator<<(std::ostream &os, const Board &board) auf
    }
    


  • Sebastian Pizer schrieb:

    Denn Du kannst Operatoren nicht überladen, wenn nicht mindestens ein benutzerdefinierter Typ für Parameter involviert ist.

    Ich gehe davon aus das du benutzerdefinierter Typ == Klasse gesetzt hast, wie class, struct, union.

    Das gilt aber nur für die globalen Operatoren, da diese nicht an eine Klasse gebunden sind und somit die vordefinierten Operatoren überdecken könnten, der Aufbau von binären Operatoren ist wie folgt:

    Operator als Klassenfunktion:

    typ_des_rückgabewerts klasse::operator[b]symbol[/b] (irgend_ein_typ par_name) { /* anweisungen */ }
    

    Operator als globale Funktion:

    typ_des_rückgabewerts operator[b]symbol[/b] (irgend_ein_typ par_name2, irgend_ein_typ par_name2) { /* anweisungen */ }
    

    Wobei ganz allgemein gilt das bei der globalen Funktion mindestens ein Paramenter ein Klassenobjekt ist.

    Zum Problem:
    Um ein zweidimensionales Array auszugeben musst du dir einen Datentypen aus class, struct oder union erstellen (wie du schon gemacht hast), dann den transfer Operator << überladen und zwar so:

    class MyArray
    {
       private:
         int m_yArray[4][4]; // nur als Beispiel
       /** initialisierungen und weitere klassenbstandteile **/
    
       public:
         ostream& operator<<(ostream& out)
         {
           for (int i=0; i<4; i++)
             for(int j=0; j<4; j++)
               out << int[i][j] << endl;
          return out;
         }
    };
    

    Dann wäre so was möglich:

    int main()
    {
     MyArray array;
     //** fülle die daten in array **/
    
     //** ausgabe von array **/
     cout << "Die Werte von MyArray: " << array;
    }
    


  • DeepCopy schrieb:

    Ich gehe davon aus das du benutzerdefinierter Typ == Klasse gesetzt hast, wie class, struct, union.

    Und enum (zumindest für einen Teil der Operatoren).

    DeepCopy schrieb:

    Sebastian Pizer schrieb:

    Denn Du kannst Operatoren nicht überladen, wenn nicht mindestens ein benutzerdefinierter Typ für Parameter involviert ist.

    Das gilt aber nur für die globalen Operatoren, da diese nicht an eine Klasse gebunden sind und somit die vordefinierten Operatoren überdecken könnten

    Bei Memberfunktionen ist this quasi ein impliziter erster Parameter, von daher kommt es wieder ungefähr hin. Aber du hast schon Recht, es ist weniger irreführend, wenn dieser Fall explizit erwähnt wird.


  • Mod

    Sebastian Pizer schrieb:

    Du musst sogar das Array in eine Klasse "packen". Denn Du kannst Operatoren nicht überladen, wenn nicht mindestens ein benutzerdefinierter Typ für Parameter involviert ist.

    Und std::ostream ist ein solcher Parameter, oder nicht?

    template <typename T, std::size_t N>
    std::ostream& operator<<(std::ostream& s, T (&a)[N])
    {
        for ( std::size_t i = 0; i != N; ++i )
            s << a[ i ];
        return s;
    }
    

    für Arrays beliebig vieler Dimensionen (T kann selbst ein Array sein). Ist allerdings wahrscheinlich keine besonders gute Idee.



  • Okay ich habs nun so:

    class Board
    {
      public:
        void drawBoard();
        std::ostream& operator<<(std::ostream&);
    
      private:
        char** board_;
        Board(const Board&);
    };
    
    void Board::drawBoard()
    {
      std::cout << "Using funktion drawBoard()." << std::endl;
      std::cout << *this;
    }
    
    std::ostream& Board::operator<<(std::ostream &os)
    {
    
      for(int iter_y=0;iter_y<height_;iter_y++)
      {
        for(int iter_x=0;iter_x<width_;iter_x++)
        {
          os << board_[iter_x][iter_y];
        }
        os << std::endl;
      }
      return os;
    }
    

    Nun erhalte ich allerdings diesen Fehler:

    Board.cpp:68: Fehler: no match für »operator<<« in »std::cout << *(Board*)this«
    

    Muss der Operator << zwei Argumente übernehmen? Wegen Verkettung von Ausgaben?
    Muss der CopyConstructor implementiert und public sein damit das ganze funktioniert?

    Danke für die vielen tollen Antworten, haben mich um ein ganzes Stück weiter gebracht.

    mfg Trust



  • Nexus schrieb:

    Bei Memberfunktionen ist this quasi ein impliziter erster Parameter

    Quasi ja, alle (nicht globalen, Polymorphie beachte mal nicht) operatoren bzw. alle Elementfunktionen im allgemeinen sind nur einmal im code-Segment des Programms für alle Objekte eines Typs enthalten, wie nun soll, bei der (early) Bindung, die Funktion wissen auf welchem Objekt es aufgerufen wird? Und dabei spielt der spezielle this-Zeiger erstmal eine untergeordnete Rolle.

    Der Compiler konvertiert implizit den Methodenaufruf von

    /** was vorher **/
       t.foo(k); // t.foo(MyClass K& k);
       /** was nacher **/
    

    nach

    /** was vorher **/
       foo(&t, &k); // t.foo()
       /** was nacher **/
    

    Damit steht für jeden Aufruf automatisch ein Benutzerdefiniert Typ zur Verfügung das ein Überladen der Standardoperatoren verhindert.
    Erst innerhalb der Funktion steht der this-Zeiger (als Formaler-Paramter) vom Typ

    IrgendEinTypVonKleinT * const this
    

    zur Verfügung, jedenfalls habe ich das mal so gelernt. 🙂



  • Hallo Trust, hab mich ein bisschen vertan, da der Transferoperator schon für die ostream Klasse definiert ist
    du aber noch deine eigene Klasse übergeben willst musst du den Transfer-Operator global definieren und
    als friend in deiner Board-Klasse deklarieren.

    Anschaulich:
    Du reist sozusagen (heimlich) im Huckepack mit der Definition der ostream-Klasse mit, oder sie mit dir,
    je nachdem wie du es sehen willst. 🙂

    #include <iostream>
    
    /** du kannst überall std:: weglassen wenn du kommende Zeile auskommentierst  **/
    // using namespace std;
    
    class Board
    {
      public:
        void drawBoard();
        Board() // wenn du einen Kopierkonstruktor angibst musst du auch 
                // einen Standardkonstruktor angeben
        {
           /* ist leer und macht nichts */
           // hier sollte board_ mit sinnvollen Werten gefüllt werden...
        } 
    
        Board(const Board&); // sollte nicht private sein, wo implementiert?
        friend std::ostream& operator<<(std::ostream&, const Board&);
    
      private:
        char** board_;
    
    };
    
    void Board::drawBoard()
    {
      std::cout << "Using funktion drawBoard()." << std::endl;
      std::cout << *this; //<-- das funktioniert jetzt wie gewünscht
    }
    
    /** jetz global und gehört nicht mehr zur Klasse Board **/
    std::ostream& operator<<(std::ostream& os, const Board& b)
    {
      /** hier fehlt aber noch was !!!**/
      //... was ist mit heigth_ und width_
    
      for(int iter_y=0;iter_y<height_;iter_y++)
      {
        for(int iter_x=0;iter_x<width_;iter_x++)
        {
          os << b.board_[iter_x][iter_y];
        }
        os << std::endl;
      }
      return os;
    }
    

    Dann kanst du auch

    cout << *this // this ist vom Typ Board
    

    schreiben.

    Einen Kopierkonstruktor brauchst du nur dann wenn Du z.B. eine "DeepCopy" 🙂 - eine tiefe Kopie haben möchtest, ich muss hier weit ausholen und kann dir nur ein kleines Beispiel geben:

    Board b(/*irgendwelche Werte für b, benötig aber einen Konstruktor*/),
      Board c = b; // hier wird KEIN Zuweisungsoperator  operator= verwendet
                   // sondern der Kopierkonstruktor, beachte den Klassentyp am 
                   // Anfang dieser Anweisung, würde da kein Board stehen wäre 
                   // es eine simple Zuweisung.
    
    // Board c(b);  // äquivalente Schreibweise für Board c = b,
    

    Das ist die Schreibweise für einen Kopierkonstruktor, die Wirkung ist (standardmässig wird einer vom Compiler generiert) ähnlich dem des Zuweisungsoperators.

    Die Wirkung ist, es werden die Elemente deiner Klasse Elementweise in dein neues Objekt kopiert, was dazu führt das nach folgender Anweisung:

    Board c = b; // c.board_ zeigt jetzt auf die selbe Adresse wie b.board_
    

    Du hast jetzt zwar zwei eigenständige Objekte b,c vom Typ Board - aber der Inhalt von board_ beider Objekte (und natürlich aller weiteren Objekte die mit dem Kopierkonstruktor initialisiert werden) zeigt auf die SELBE Speicheradresse, dass heisst wenn du:

    b.board_[0][0] = 'a';
    

    ausführst dann steht im board_ deines ANDEREN Objekts genau das gleiche, das gilt im übrigen auch für Zuweisungsoperatoren wenn es sich um Zeiger handelt.

    c.board_[0][0] == 'a'; // ergibt true weil c und b auf die selbe Adresse zeigen
    

    Tut mir leid dass das jetzt so umfangreich geworden ist, aber dieses Problem hat man mit allen Referenzen (sprich Zeigern), und damit solltest du dich noch intensiv befassen, Zeiger sind ohnehin ein ganzes Kapitel für sich.

    Um damit zur Antwort zu kommen, für dein obiges Beispiel brauchst du erstmal keinen Kopierkonstruktor. 😃



  • Danke Danke Danke!

    Es funktioniert perfekt :).

    Tut mir leid dass das jetzt so umfangreich geworden ist, aber dieses Problem hat man mit allen Referenzen (sprich Zeigern), und damit solltest du dich noch intensiv befassen, Zeiger sind ohnehin ein ganzes Kapitel für sich.

    Ich meinte nur ob ich für mein Beispiel einen CopyConstructor brauche. Brauch ich aber eh nicht.

    Danke für die tolle Erklärung und auch danke, dafür dass du direkt meinen Code so bearbeitet hast, dass er korrekt ist.

    Die Erklärung des CopyConstructors ist deinem Nick DeepCopy würdig 😃

    Also danke an alle die an der Lösung meines Problems mit geholfen haben. 👍

    mfg Trust


Anmelden zum Antworten