Matrizendarstellung in C++



  • Hallo Leute,

    ich möchte ein Template zur Matrizendarstellung implementieren. Der Zahlentyp und die Anzahl der Zeilen und Spalten sollen als Templateparameter angegeben werden.
    Die Klasse soll auch ein Ausgabeoperator, sowie Addition- und Subtraktionsoperatoren als friend Operatoren. Instanziieren will ich den Datentypen mit unsigned int und anwenden will ich das auf meine Klassen Rational und Complex.

    Matrix<Rational,2,3> m;

    Die Ausgabe von m sollte wie folgt aussehen:

    1/3 3/5 17/18
    3/4 5/2 -6/7

    Bei Matrix<Complex,2,1> m2 könnte eine Ausgabe wie folgt aussehen:

    3+i4
    6+i2

    
    #include<iostream>
    using namespace std;
    
    template<class T, unsigned uiRows, unsigned uiColumns>
    class Matrix{
    public:
    
        Matrix():
            m_Elem(new T[uiRows * uiColumns]){}
    
        ~Matrix(){
    
        }
    
        Matrix& operator=(const Matrix&) = delete;
        Matrix(const Matrix&) = delete;
    
        friend ostream& operator<<(ostream& os, const Matrix& crArg){
            for(unsigned uiCol = 0; uiCol < uiColumns; ++uiCol){
                os << crArg.m_Elem;
            }
            return os;
        }
    
    
    //    friend Matrix& operator++(const Matrix& crArg){}
    
    //    friend Matrix& operator--(const Matrix& crArg){}
    
    private:
        T* m_Elem;
    };
    
    class Rational{
    public:
        Rational(int iZaehler, unsigned iNenner = 1):
            m_iZaehler(iZaehler),
            m_iNenner(iNenner){
                kuerzen();
            }
    
        int ggT(int iZaehler, unsigned iNenner){
            if(iNenner == 0)
                return iZaehler;
            else return ggT(iNenner, iZaehler % iNenner);
        }
    
        void kuerzen(){
            int iTemp(ggT(m_iZaehler, m_iNenner));
            m_iZaehler /= iTemp;
            m_iNenner /= iTemp;
        }
    
        friend Rational operator+(const Rational& crArg1, const Rational& crArg2){
            return Rational((crArg1.m_iZaehler  * crArg2.m_iNenner + crArg1.m_iNenner * crArg2.m_iZaehler),
                            (crArg1.m_iNenner * crArg2.m_iNenner));
        }
    
        friend Rational operator-(const Rational& crArg1, const Rational& crArg2){
            return Rational((crArg1.m_iZaehler  * crArg2.m_iNenner - crArg1.m_iNenner * crArg2.m_iZaehler),
                            (crArg1.m_iNenner * crArg2.m_iNenner));
        }
    
        friend Rational operator*(const Rational& crArg1, const Rational& crArg2){
            return Rational((crArg1.m_iZaehler  * crArg2.m_iZaehler),
                            (crArg1.m_iNenner * crArg2.m_iNenner));
        }
    
        friend Rational operator/(const Rational& crArg1, const Rational& crArg2){
            return Rational((crArg1.m_iZaehler * crArg2.m_iNenner),
                            (crArg1.m_iNenner * crArg2.m_iZaehler));
        }
    
         friend ostream& operator<<(ostream& os, const Rational& crArg){
             os << crArg.m_iZaehler << "/" << crArg.m_iNenner << endl;
             return os;
        }
    private:
        int m_iZaehler;
        unsigned m_iNenner;
    };
    
    class Complex{
    public:
        Complex(double dReal, double dImag):
            m_dReal(dReal),
            m_dImag(dImag){}
    
        friend Complex operator+(const Complex& crArg1, const Complex& crArg2){
            return Complex(crArg1.m_dReal + crArg2.m_dReal,
                           crArg1.m_dImag + crArg2.m_dImag);
        }
    
        friend Complex operator-(const Complex& crArg1, const Complex& crArg2){
            return Complex(crArg1.m_dReal - crArg2.m_dReal,
                           crArg1.m_dImag - crArg2.m_dImag);
        }
    
        friend Complex operator*(const Complex& crArg1, const Complex& crArg2){
            return Complex((crArg1.m_dReal * crArg2.m_dReal - crArg1.m_dImag * crArg2.m_dImag),
                           (crArg1.m_dReal * crArg2.m_dImag + crArg1.m_dImag * crArg2.m_dReal));
        }
    
        friend Complex operator/(const Complex& crArg1, const Complex& crArg2){
            const double cdNenner = crArg2.m_dReal * crArg2.m_dReal + crArg2.m_dImag * crArg2.m_dImag;
            return Complex((crArg1.m_dReal * crArg2.m_dReal + crArg1.m_dImag * crArg2.m_dImag) / cdNenner,
                           (crArg1.m_dReal * crArg2.m_dImag - crArg1.m_dImag * crArg2.m_dReal) / cdNenner);
        }
    
         friend ostream& operator<<(ostream& os, const Complex& crArg){
             os << "(" << crArg.m_dReal << " + " << crArg.m_dImag << "i)";
             return os;
        }
    
    
    private:
        double m_dReal;
        double m_dImag;
    };
    
    int main(){
        Rational r1(1,3);
        Rational r2(3,5);
        Rational r3(17,18);
        Rational r4(3,4);
        Rational r5(5,2);
        Rational r6(-6,7);
    
        Complex c1(3.0,4.0);
        Complex c2(6.0,2.0);
    
            //Zeilen und Spalten
        Matrix<Rational,2,3> m;
    
        cout << m << endl;
        return 0;
    }
    
    


  • Aha



  • @KelbsSohn sagte in Matrizendarstellung in C++:

    template<class T, unsigned uiRows, unsigned uiColumns>
    // ...
        Matrix():
            m_Elem(new T[uiRows * uiColumns]){}
    
        ~Matrix(){
    
        }
    

    🤦♂



  • @KelbsSohn sagte in Matrizendarstellung in C++:

    ich möchte ...
    Der Zahlentyp und die Anzahl der Zeilen und Spalten sollen ...
    Die Klasse soll ...
    Instanziieren will ich...
    anwenden will ich ...
    Die Ausgabe von m sollte ...
    Bei Matrix<Complex,2,1> m2 könnte ...

    Ist ja alles schön und gut. Aber hast du auch eine Frage?



  • Eventuell naheliegende Fragen:

    • Was ist std::array<>?
    • Was ist std::vector<>?
    • Warum sind Smartpointer toll?
    • Was ist die Rule of Zero?
    • Warum ist die Hungarian Notation kacke?




  • Es fehlt noch mindestens:
    Was ist std::complex?



  • Meine Frage ist nur wie ich das Template dafür gestalte, komme da einfach nicht weiter.

    Aber dass die Leute hier einen so attackieren hätte ich nicht gedacht.

    Bin kein Programmierer, bin noch am lernen.

    LG



  • @KelbsSohn Wer hat Dich attackiert? Hier sehe ich keine Attacke. Wenn Deine Klassenkameraden Dich attackieren, können wir doch nix dafür.



  • @mgaeckler Naja, ich publiziere mein Code den ich geschrieben habe und bekomme als Antwort Aha, Facepalm. Anstatt direkt zu sagen, "hey, du hast deine Frage vergessen" oder sowas in der Art. Aber egal, ich möchte nur einen Ansatz und keine geschriebene Lösung.

    LG



  • Naja, der Facepalm war ein Hinweis auf einen offensichtlichen Fehler in deiner Klasse. Im Konstruktor wird Speicher reserviert, im Destruktor nicht wieder freigegeben.

    Da du keine Frage gestellt hattest, hast du eben Antworten bekommen zu Dingen, die falsch in deinem Code sind oder verbesserungswürdig sind. @Swordfish hat in seiner Antwort ein paar Fragen gestellt. Die Idee hinter den Fragen ist natürlich, dass du dich damit befasst. Hättest du z.B. std::vector verwendet, wäre das Speicherleck nicht passiert. Für deine getemplatete Klasse kannst du dann std::array verwenden. Und generell lies zu diesem Thema über die rule of zero (rule of three / rule of five). Und so weiter.

    Womit hast du denn beim Template Probleme? Was genau meinst du mit "wie ich das Template gestalte"? Also am besten intern mit einem std::array arbeiten, würde ich sagen, denn die Dimensionen stehen ja fest.

    Merke: es ist schlecht, Code hier einzustellen und dann mit vielen Konjunktiven zu beschreiben. Dann weiß niemand, was du genau wissen willst und du bekommst dann eben irgendwelche Antworten zu Themen, die mit deinem Code zu tun haben und dem Antwortschreiber eben gerade in den Kopf kommen.



  • @wob Ja, ich hatte da die Freigabe des Speichers, hab ich aber weggenommen, weil ich nicht sicher bin, ob ich mit dem Pointer-Array weiterarbeite bzw. ob das überhaupt möglich ist.

    Ich möchte auch keine std::vector, std::array usw. benutzen. Es geht mir nur darum, ich habe eine Complex-Klasse und eine Rational-Klasse. Dafür möchte ich ein Template Matrizen schreiben, der mir die Ausgabe beider Klassen als Matrizen darstellt.

    Und danke, werde dies berücksichtigen bevor ich ein Topic eröffne. 🙂



  • Meine Frage ist nur wie ich das Template dafür gestalte, komme da einfach nicht weiter.
    ich möchte ein Template zur Matrizendarstellung implementieren.

    OK. Noch nicht fertig, aber den template<class T, unsigned uiRows, unsigned uiColumns> Teil hast du schonmal. Das ist so OK.

    Der Zahlentyp und die Anzahl der Zeilen und Spalten sollen als Templateparameter angegeben werden.

    Hast du schon, passt auch.

    Die Klasse soll auch ein Ausgabeoperator, sowie Addition- und Subtraktionsoperatoren als friend Operatoren.

    Signatur für den Ausgabeoperator hast du auch schon, sieht auch OK aus. Die Implementierung ist halt falsch weil du nur eine Schleife für die Spalten hast - du brauchst aber eine Schleife für die Spalten und darin nochmal eine für die Zeilen. Oder umgekehrt, auf jeden Fall zwei verschachtelte Schleifen. (OK, es geht auch mit einer Schleife, aber es ist vermutlich einfacher mit zwei.)

    Für Addition und Subtraktion hast du die falschen Operatoren versucht (++ und --).

        friend Matrix operator+(const Matrix& a, const Matrix& b){
            Matrix result;
            // compute result
            return result;
        }
    
        friend Matrix operator-(const Matrix& a, const Matrix& b) {
            Matrix result;
            // compute result
            return result;
        }
    

    Instanziieren will ich den Datentypen mit unsigned int und anwenden will ich das auf meine Klassen Rational und Complex.

    Für mich nicht verständlich was du damit meinst. Was ist hier der Unterschied zwischen instanziieren und anwenden?

    Ein paar allgemeine Tips noch:

    • Vergiss dynamische Speicheranforderung mit new. Die Grösse der Matrix ist ja über Templateparameter gegeben, d.h. du kannst z.B. einfach std::array verwenden.
    • Bau dir als erstes mal eine Funktion zum Zugriff auf ein Element über row-number und column-number
    • Fang erstmal damit an nur das Matrix-Template zu implementieren und vergiss die Rational und Complex Klassen. Fang mit Matrix<double, ...> an. Wenn das dann soweit passt, dann kannst du mit Rational, Complex etc. weitermachen.

    Beispiel wie das mit std::array aussehen könnte:

    template <class T, unsigned uiRows, unsigned uiColumns>
    class Matrix {
    public:
        Matrix() : m_elements{} {}
        // compilergenerierter Destruktor ist OK
        // compilergenerierter Assignment-Operator ist OK
        // compilergenerierter Move-Operator ist OK
    
        friend ostream& operator<<(ostream& os, const Matrix& crArg) {
            for (unsigned row = 0; row < uiRows; row++) {
                for (unsigned column = 0; column < uiColumns; column++) {
                    T const& e = get(row, column);
                    // ...
                }
            }
        }
    
        friend Matrix operator+(const Matrix& a, const Matrix& b) { ... }
        friend Matrix operator-(const Matrix& a, const Matrix& b) { ... }
    
        T& get(unsigned row, unsigned column) {
            return m_elements[row * uiColumns + column];
        }
    
        T const& get(unsigned row, unsigned column) const {
            return m_elements[row * uiColumns + column];
        }
    
    private:
        std::array<T, uiRows * uiColumns> m_elements;
    };
    


  • @KelbsSohn sagte in Matrizendarstellung in C++:

    Ich möchte auch keine std::vector, std::array usw. benutzen.

    Dann nimm ein normales Member-Array statt dessen. Kein dynamisch erzeugtes.



  • Dieser Beitrag wurde gelöscht!


  • @Swordfish sagte in Matrizendarstellung in C++:

    int, unsigned, doch was anderes? Entscheide Dich.

    Stimmt. Fixed.

    ps: Beim Editieren hat mich grad eiskalt eine mir bisher noch nicht bekannte Funktion des Forums erwischt: wenn man per Copy+Paste etwas einfügt was mit > anfängt, dann wird automatisch > vor allen folgenden Zeilen des kopierten Texts eingefügt. Da ich's erst bemerkt habe nachdem ich schon auf "Absenden" gedrückt hab durfte ich das dann alles wieder per Hand wegmachen. 😞



  • @KelbsSohn sagte in Matrizendarstellung in C++:

    Ich möchte auch keine std::vector, std::array usw. benutzen.

    Dann läuft das auf eine Allocator Klasse hinaus, wenn man es denn „richtig“ machen will.



  • @john-0 sagte in Matrizendarstellung in C++:

    @KelbsSohn sagte in Matrizendarstellung in C++:

    Ich möchte auch keine std::vector, std::array usw. benutzen.

    Dann läuft das auf eine Allocator Klasse hinaus, wenn man es denn „richtig“ machen will.

    Warum? Hier wird doch überhaupt kein dynamischer Speicher benötigt, da die Dimensionen Template-Parameter sind und somit mit T data[rows * cols] ein Array angelegt werden kann. Wo muss da ein Allocator ins Spiel kommen?



  • Vielleicht möchte man sich bei der Matrixgröße nicht durch den Stack einschränken lassen.



  • @wob sagte in Matrizendarstellung in C++:

    Warum? Hier wird doch überhaupt kein dynamischer Speicher benötigt, da die Dimensionen Template-Parameter sind und somit mit T data[rows * cols] ein Array angelegt werden kann. Wo muss da ein Allocator ins Spiel kommen?

    std::array & Co. eliminiert die Möglichkeit die Move-Semantik bei nicht trivialen Matrizendimensionen zu nutzen, dazu hat move auf allen Standardcontainer mit dem Standard Allocator O(1) Laufzeitverhalten. Das geht mit std::array nicht, da hat man ohne wirklichen Grund O(M×N).

    Sicherlich lassen sich mit viel Template Metaprogramming auch zwei Matrizen korrekt multiplizieren, wenn man hier nicht nur Hadamard-Produkt implementieren will. Für Anfänger dürfte das mit dynamsicher Allokation während der Laufzeit und simplen Größenvergleichen leichter umzusetzen sein.


Anmelden zum Antworten