Matrix-Klasse: Überladen von Operatoren

timestamp

Mitglied Käsekuchen
Hallo Forum,

ich versuche mich derzeit an einer Matrizenklasse und habe dazu mehrere Fragen:
Zunächst der Code:
C++:
#ifndef MATRIX_H
#define MATRIX_H
#include <ostream>

class Matrix{
    public:
        /** Default constructor */
        Matrix(int m, int n);
        Matrix(const Matrix& A);
        ~Matrix();
        Matrix& operator+=(Matrix& A);
        Matrix& operator-=(Matrix& A);
        Matrix& operator*=(Matrix& A);
        Matrix& operator*=(double d);
        Matrix& operator/=(double d);
        Matrix& operator=(Matrix& A);
        double& operator()(int i, int j) const;

        friend std::ostream& operator<<(std::ostream& out, Matrix& A);
        friend Matrix& operator+( Matrix& A,  Matrix& B);
        friend Matrix& operator-( Matrix& A,  Matrix& B);
        friend Matrix& operator*( Matrix& A,  Matrix& B);
        friend Matrix& operator*( Matrix& A, double d);
        friend Matrix& operator*(double d,  Matrix& A);
        friend Matrix& operator/( Matrix& A, double d);

        Matrix transpose();
        Matrix inverse();

        inline int n(){ return _n; };
        inline int m(){ return _m; };
        //Matrix gauss(Vector& v);
    protected:
    private:
        double** _matrix;
        int _m, _n;
};
#endif // MATRIX_H

C++:
//Matrix.cpp
#include "Matrix.h"
#include <cassert>
#include <ostream>
#include <iostream>
Matrix::Matrix(int m, int n) : _m(m), _n(n){
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n] {};
    }
}
Matrix::Matrix(const Matrix& A){
    _m = A._m;
    _n = A._n;
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n] {};
        for( int j = 0; j < _n; j++ ){
            (*this)(i, j) = A(i, j);
        }
    }
}
Matrix::~Matrix(){
    delete _matrix;
}
Matrix& Matrix::operator+=(Matrix& A){
    assert(_m == A._m && _n == A._n);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            (*this)(i, j) += A(i,j);
        }
    }
    return *this;
}
Matrix& Matrix::operator-=(Matrix& A){
    assert(_m == A._m && _n == A._n);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            (*this)(i, j) -= A(i,j);
        }
    }
    return *this;
}

Matrix& Matrix::operator=(Matrix& A){
    delete _matrix;
    _m = A._m;
    _n = A._n;
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n];
        for( int j = 0; j < _n; j++ ){
            _matrix[i][j] = A(i, j);
        }
    }
    return *this;
}

Matrix& operator+(Matrix& A, Matrix& B){
    assert( A._m == B._m && A._n == B._n );
    Matrix C(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            C(i,j) += B(i,j);
        }
    }
    return C;
}

Matrix& operator-(Matrix& A, Matrix& B){
    assert( A._m == B._m && A._n == B._n );
    Matrix C(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            C(i,j) = A(i,j)-B(i,j);
        }
    }
    return C;
}

Matrix& Matrix::operator*=(double d){
    for( int i = 0; i < _m; i++ ){
        for( int j = 0; j < _n; j++ ){
            (*this)(i, j) *= d;
        }
    }
    return *this;
}

Matrix& operator*(Matrix& A, double d){
    Matrix B(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            B(i, j) *= d;
        }
    }
    return B;
}

double& Matrix::operator()(int i, int j) const{
    assert(i >= 0 && i < _m);
    assert(j >= 0 && j < _n);
    return _matrix[i][j];
}

std::ostream& operator<<(std::ostream& out, Matrix& A){
    for( int i = 0; i < A._m; i++ ){
        out << "( ";
        for( int j = 0; j < A._n; j++ ){
            if( j > 0 ){
                out << "\t";
            }
            out << A(i, j);
        }
        out << ")" << std::endl;
    }
    return out;
}

C++:
//main.cpp
#include <iostream>
#include <typeinfo>
#include "Matrix.h"
#include "MathLib.h"

using namespace std;

int main(){
    Matrix A(2, 2);
    Matrix B(2, 2);
    A(0, 0) = 1;
    A(0, 1) = 0;
    A(1, 0) = 0;
    A(1, 1) = -1;
    B(0, 0) = 1;
    B(0, 1) = 2;
    B(1, 0) = 3;
    B(1, 1) = 4;
    A = B;
    cout << A; // Liefert B
    Matrix C = A+B;
    cout << C; // Gibt Null Matrix
    Matrix D(C);
    cout << D; // Gibt Null Matrix
    Matrix E(A+B+C);
    cout << E; // Gibt Null Matrix
    return 0;
}


Meine Fragen dazu:
1) Ist der Dekonstruktor überhaupt notwendig?
2) Ich möchte auf jeden Fall Chaining von Operatoren erlauben. Dazu muss ich ja eine Referenz (Matrix&) zurückgeben, oder? Scheinbar klappt das aber nicht so ganz, denn die Matrizen die ich erhalte sind ja alle 0. Wenn ich keine Referenz zurückgebe (ich brauche sogar nicht mal ein return) dann klappt die Addition. Chaining schlägt allerdings fehl.
3) Schreibe ich cout<<A+B; dann schlägt die Assertion (i>=0 && i <= _m) fehl und mein Programm stürzt ab. Das verstehe ich auch nicht.
 
Da sind einige Probleme drin ( :) )

---

Zuerst einmal drei Arten (nicht komplett), wie Funktionsparameter übergeben werden können:
C++:
void funk(int a) {}
...
int i = 1;
funk(i);
funk(123);
Ganz "normale" Übergabe, einmal von einer Variable, einmal von einem Literal. Bei Übergabe wird der Wert kopiert, ändern in der Funktion ist zwar möglich aber wirkt sich nicht auf das Original aus (= geänderte Version verschiwndet bei Funktionsende).
C++:
void funk(int &a) {}
...
int i = 1;
funk(i);
funk(123);//x
Referenz. Bei Übergabe wird der Wert "nicht" kopiert, ändern in der FUnktion ist möglich und wirkt sich auf das Original aus. Aber: Von den zwei Aufrufen ist nur der mit der Variable i gültig, weil es beim fixen Wert 123 kein "Ziel" gibt, wo man die Änderungen hinspeichern kann (auch, wenn die Funktion gar nichts ändert, ist egal. Compilerfehler).
C++:
void funk(const int &a) {}
...
int i = 1;
funk(i);
funk(123);
Const-Referenz. Bei Übergabe wird der Wert "nicht" kopiert, ändern in der Funktion ist "nicht" möglich (ist ja der Sinn von const). ... Und damit kann auch 123 wieder problemlos übergeben werden.

(Pointer und R-Value-Referenzen lass ich hier weg)

Was das bei deinem Code ausmacht: Deine ganzen Operatoren nehmen constlose Referenzen, dH. der Parameter muss irgendwo Speicherplatz zum ändern haben. Kein fixes Ding wie 123, und auch kein Rechenergebnis längerer Ausdrücke (solang nicht zuerst in eine ordentliche Variable gespeichert). Du schreibst ja, dass Nicht-Referenz-Returnwerte nicht funktionieren, eben deswegen: Du kannst die Zwischenergebnisse gechainten Ausdrucke nicht an weitere Operatoren übergeben >= Compilerfehler.

Lösung: Parameter, die in der Funktion sowieso nicht geändert werden, const machen. Sind viele bei dir, Nicht nur besserer Stil und mehr Möglichkeiten, durch Compilermeldungen unbeabsichtige Änderungen von Variablen im Code zu finden, sondern es ermöglicht bestimmten Code eben erst auch.

---

Im Zusammenhang damit: Sollen Returnwerte Referenzen sein? Für die meisten Fälle gilt:

Bei Operatoren wie += die das this-Objekt auch ändern: Ja (und returned wird eben *this)

Bei Operatoren wie + die ein Zwischenergebnis am Stack erzeugen, statt die zwei beteiligten Objekte zu ändern:
Nein, Fehler, weil: Das, was returned wird, ist ja im Normalfall eine lokale Variable in der Operatorfunktion (angelegt, mit dem Rechenergebnis gefüllt, return). Eine Referenz ist ja per Definition keine Kopie, sondern im Inneren etwas Pointerartiges, nur mit schönerer Syntax. Wenn du jetzt eine Referenz auf eine "lokale" Variable zurückgibst, die beim Returnen = am Funktionsende schon weggeräumt wird? Genau, jeder Zugriff später im Main geht irgendwo hin, wo nichts mehr ist.

Dürfte der Gründ für die Abstürze und falschen Werte sein, dein Objekt gibt ja beim Destruktor
auch den reservierten Speicher frei (der vom nächsten angelegten Objekt gleich wieder verwendet werden kann)

=> Änderungsbedarf für + - usw. (aber += -= sind ok), keine & in den Returnwerten

Btw. Compilerwarnungen aktivieren ist nichts Schlechtes (außer man verwendet VisualStudio :/).

---

Noch ein kleineres const-Ding: Beim operator() ist ja die ganze Funktion const, also es werden keine Änderungen am this-Objekt gemacht/erlaubt. Erstens trifft das auch mehr Funktionen zu, evt. die auch const machen (auch wenns so nicht falsch ist). Zweitens ist es genau genommen zwar kein (Compiler-)Fehler, aber etwas const zu machen, dass änderbare Referenzen auf die Matrixwerte zurückgibt...?

---

Der Destruktor ist nötig, ja, und falsch.
Angelegt wird dein Datending mit einem "großen" new und dann eine Schleife mit new´s, also muss es mit einer Schleife von delete[] und dann einem großen delete[] wieder weggeräumt werden. Ein einzelnes delete[] reicht nicht. Auch beim operator= (und evt. noch wo anders) wird sowas gemacht.

Zusätzlich gibt es einen wesentlichen Unterschied zwischen delete und delete[]. Ersteres ist für Einzelvariablen, die mit new angelegt wurden, letzteres für Arrays von "new blabla[sizesize]". Die falsche delete-Variante kann zu Problemen führen, das ist nicht nur ein Schönheitsfehler.

Um sich für new/delete und Destruktoren zu ersparen könnte man std::vector verwenden. Das macht zwar in sich nichts Anderes, aber man hat den fehleranfälligen Teil dorthin ausgelagert (und vector ist natürlich von ziemlich viel Leuten getestet).

---

Statt Assert könnte man normale C++-Exceptions verwenden, die dann auch bei Bedarf codemäßig im main etc. behandelbar sind statt immer das ganze Programm zu killen.

---

Die Matrixmultiplikation ist mathematisch falsch.

---

Bei Gelegenheit wären ein paar Funktionen mit R-Value-Referenzen nicht schlecht, nämlich eine Variante von Konstruktor und operator= damit (die "Big5"-Funktionen: Normalkonstruktor, CopyK., RVR-K., operator= normal, operator= mit RVR ... und Destruktor als Nummer 6)

---

Für die Möglichkeit, von dieser Klasse zu erben: Den Destruktor virtual machen.

---
So.
 
Zuletzt bearbeitet:
Hallo,
also der Destruktor ist definitiv notwendig. Zu dem anderen hat sheel ja schon was geschrieben.

Immer wenn "new" verwendet wird, dann sollte auch ein delete folgen. Und der beste Ort dafür ist in einer Klasse der Destruktor.
Dazu kommt, dass dein Destruktor auch nicht richtig deleted. Du gibst nur den Speicher frei, den das erste new allokiert hat. Du müsstest erst mal per Schleife den Speicher aus der for-Schleife und anschließend den vom ersten "new" freigeben.
 
Hallo,

tut mir Leid ich habe etwas länger gebraucht.

Da sind einige Probleme drin ( :) )

Das habe ich mir schon gedacht, danke dass du das alles so schön auseinander genommen hast :)
---

Zuerst einmal drei Arten (nicht komplett), wie Funktionsparameter übergeben werden können:
C++:
void funk(int a) {}
...
int i = 1;
funk(i);
funk(123);
Ganz "normale" Übergabe, einmal von einer Variable, einmal von einem Literal. Bei Übergabe wird der Wert kopiert, ändern in der Funktion ist zwar möglich aber wirkt sich nicht auf das Original aus (= geänderte Version verschiwndet bei Funktionsende).
C++:
void funk(int &a) {}
...
int i = 1;
funk(i);
funk(123);//x
Referenz. Bei Übergabe wird der Wert "nicht" kopiert, ändern in der FUnktion ist möglich und wirkt sich auf das Original aus. Aber: Von den zwei Aufrufen ist nur der mit der Variable i gültig, weil es beim fixen Wert 123 kein "Ziel" gibt, wo man die Änderungen hinspeichern kann (auch, wenn die Funktion gar nichts ändert, ist egal. Compilerfehler).
C++:
void funk(const int &a) {}
...
int i = 1;
funk(i);
funk(123);
Const-Referenz. Bei Übergabe wird der Wert "nicht" kopiert, ändern in der Funktion ist "nicht" möglich (ist ja der Sinn von const). ... Und damit kann auch 123 wieder problemlos übergeben werden.

(Pointer und R-Value-Referenzen lass ich hier weg)

Was das bei deinem Code ausmacht: Deine ganzen Operatoren nehmen constlose Referenzen, dH. der Parameter muss irgendwo Speicherplatz zum ändern haben. Kein fixes Ding wie 123, und auch kein Rechenergebnis längerer Ausdrücke (solang nicht zuerst in eine ordentliche Variable gespeichert). Du schreibst ja, dass Nicht-Referenz-Returnwerte nicht funktionieren, eben deswegen: Du kannst die Zwischenergebnisse gechainten Ausdrucke nicht an weitere Operatoren übergeben >= Compilerfehler.

Lösung: Parameter, die in der Funktion sowieso nicht geändert werden, const machen. Sind viele bei dir, Nicht nur besserer Stil und mehr Möglichkeiten, durch Compilermeldungen unbeabsichtige Änderungen von Variablen im Code zu finden, sondern es ermöglicht bestimmten Code eben erst auch.

---
Aha! Super! Da wird mir einiges klarer.

Im Zusammenhang damit: Sollen Returnwerte Referenzen sein? Für die meisten Fälle gilt:

Bei Operatoren wie += die das this-Objekt auch ändern: Ja (und returned wird eben *this)

Bei Operatoren wie + die ein Zwischenergebnis am Stack erzeugen, statt die zwei beteiligten Objekte zu ändern:
Nein, Fehler, weil: Das, was returned wird, ist ja im Normalfall eine lokale Variable in der Operatorfunktion (angelegt, mit dem Rechenergebnis gefüllt, return). Eine Referenz ist ja per Definition keine Kopie, sondern im Inneren etwas Pointerartiges, nur mit schönerer Syntax. Wenn du jetzt eine Referenz auf eine "lokale" Variable zurückgibst, die beim Returnen = am Funktionsende schon weggeräumt wird? Genau, jeder Zugriff später im Main geht irgendwo hin, wo nichts mehr ist.

Dürfte der Gründ für die Abstürze und falschen Werte sein, dein Objekt gibt ja beim Destruktor
auch den reservierten Speicher frei (der vom nächsten angelegten Objekt gleich wieder verwendet werden kann)

=> Änderungsbedarf für + - usw. (aber += -= sind ok), keine & in den Returnwerten

Btw. Compilerwarnungen aktivieren ist nichts Schlechtes (außer man verwendet VisualStudio :/).

---
Es kann so einfach sein, wenn man es mal versteht. Macht deutlich mehr Sinn so! :D

Noch ein kleineres const-Ding: Beim operator() ist ja die ganze Funktion const, also es werden keine Änderungen am this-Objekt gemacht/erlaubt. Erstens trifft das auch mehr Funktionen zu, evt. die auch const machen (auch wenns so nicht falsch ist). Zweitens ist es genau genommen zwar kein (Compiler-)Fehler, aber etwas const zu machen, dass änderbare Referenzen auf die Matrixwerte zurückgibt...?

---
Ich brauchte das const, weil sonst in einigen Methoden gemeckert wurde. Ich habe das inzwischen aber auch durch eine Überladung geändert.

Der Destruktor ist nötig, ja, und falsch.
Angelegt wird dein Datending mit einem "großen" new und dann eine Schleife mit new´s, also muss es mit einer Schleife von delete[] und dann einem großen delete[] wieder weggeräumt werden. Ein einzelnes delete[] reicht nicht. Auch beim operator= (und evt. noch wo anders) wird sowas gemacht.

Zusätzlich gibt es einen wesentlichen Unterschied zwischen delete und delete[]. Ersteres ist für Einzelvariablen, die mit new angelegt wurden, letzteres für Arrays von "new blabla[sizesize]". Die falsche delete-Variante kann zu Problemen führen, das ist nicht nur ein Schönheitsfehler.

Um sich für new/delete und Destruktoren zu ersparen könnte man std::vector verwenden. Das macht zwar in sich nichts Anderes, aber man hat den fehleranfälligen Teil dorthin ausgelagert (und vector ist natürlich von ziemlich viel Leuten getestet).

---
Oh! Alles klar, wurde auch behoben. Ich habe viel über vector gelesen und auch früher schon mit gearbeitet. In diesem Projekt möchte ich aber gezielt nochmal mich mehr mit C++ beschäftigen und einige Sachen des Verständnisses halber selbst implementieren. Deswegen wollte ich (vorerst) bei Arrays bleiben.
Statt Assert könnte man normale C++-Exceptions verwenden, die dann auch bei Bedarf codemäßig im main etc. behandelbar sind statt immer das ganze Programm zu killen.

---
Da hast du natürlich Recht. Da war ich jetzt nur zu faul zu :p
Die Matrixmultiplikation ist mathematisch falsch.

---
Oben abgebildet ist die Skalarmultiplikation^^
Bei Gelegenheit wären ein paar Funktionen mit R-Value-Referenzen nicht schlecht, nämlich eine Variante von Konstruktor und operator= damit (die "Big5"-Funktionen: Normalkonstruktor, CopyK., RVR-K., operator= normal, operator= mit RVR ... und Destruktor als Nummer 6)

---
Da muss ich mich nochmal einlesen.
Für die Möglichkeit, von dieser Klasse zu erben: Den Destruktor virtual machen.

---
So.
Vielen Dank nochmal für deine ausführlichen Beschreibungen. An dieser Stelle, natürlich auch Danke an Jennesta.

Hier also der aktualisierte (und jetzt auch umfangreichere) Code:

C++:
/** Matrix.h **/
#ifndef MATRIX_H
#define MATRIX_H
#include <ostream>

class Matrix{
    public:
        enum class Type { ID };
        const bool FORMAT_ROW = true;
        const bool FORMAT_COL = true;
        /** Default constructor */
        Matrix(int m, int n);
        Matrix(const Matrix& A);
        Matrix(int m, Matrix::Type t);
        //Matrix(bool format, double data[], ...);
        ~Matrix();
        Matrix& operator+=(const Matrix& A);
        Matrix& operator-=(const Matrix& A);
        Matrix& operator*=(const Matrix& A);
        Matrix& operator*=(double d);
        Matrix& operator/=(double d);
        Matrix& operator=(const Matrix& A);
        double& operator()(int i, int j);
        double operator()(int i, int j) const;

        Matrix multiplyRow(int row, double factor);
        Matrix multiplyCol(int col, double factor);
        Matrix divideRow(int row, double factor);
        Matrix divideCol(int col, double factor);

        Matrix addRow(int row1, int row2);
        Matrix addRow(int row1, double f1, int row2, double f2);
        Matrix subRow(int row1, int row2);
        Matrix subRow(int row1, double f1, int row2, double f2);
        Matrix addCol(int col1, int col2);
        Matrix addCol(int col1, double f1, int col2, double f2);
        Matrix subCol(int col1, int col2);
        Matrix subCol(int col1, double f1, int col2, double f2);


        friend std::ostream& operator<<(std::ostream& out, const Matrix& A);
        friend Matrix operator+(const Matrix& A, const Matrix& B);
        friend Matrix operator-(const Matrix& A, const Matrix& B);
        friend Matrix operator*(const Matrix& A, const Matrix& B);
        friend Matrix operator*(const Matrix& A, double d);
        friend Matrix operator*(double d, const Matrix& A);
        friend Matrix operator/(const Matrix& A, double d);
        friend double det(const Matrix& A);

        double determinant() const;
        Matrix transpose() const;
        Matrix inverse() const;

        inline int n() const { return _n; };
        inline int m() const { return _m; };
        //Matrix gauss(Vector& v);
    protected:
    private:
        double** _matrix;
        int _m, _n;
};
#endif // MATRIX_H

C++:
// Matrix.cpp
#include "Matrix.h"
#include "Permutation.h"
#include <cassert>
#include <ostream>
#include <string>
Matrix::Matrix(int m, int n) : _m(m), _n(n){
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n] {};
    }
}

Matrix::Matrix(const Matrix& A){
    _m = A._m;
    _n = A._n;
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n] {};
        for( int j = 0; j < _n; j++ ){
            (*this)(i, j) = A(i, j);
        }
    }
}
Matrix::Matrix(int n, Matrix::Type t):_m(n),_n(n){
    _matrix = new double*[_n];
    for( int i = 0; i < _n; i++ ){
        _matrix[i] = new double[_n] {};
        _matrix[i][i] = 1;
    }
}
Matrix::~Matrix(){
    for( int i = 0; i < _m; i++ ){
        delete[] _matrix[i];
    }
    delete[] _matrix;
}

Matrix Matrix::transpose() const{
    Matrix A(_n, _m);
    for( int i = 0; i < _n;  i++ ){
        for( int j = 0; j < _m; j++ ){
            A(i, j) = (*this)(j, i);
        }
    }
    return A;
}

double Matrix::determinant() const{
    assert(_m == _n);
    if( _n == 1 )
        return (*this)(0, 0);
    if( _n == 2 )
        return (*this)(0, 0) * (*this)(1, 1) - (*this)(0, 1) * (*this)(1, 0);
    double determinant = 0, product;
    Permutation p(_n);
    int mx = p.amount();
    for( int i = 0; i < mx; i++ ){
        product = p.sign();
        for( int j = 0; j < _n; j++ ){
            product *= (*this)(j, p[j]-1);
            if( product == 0 ){
                break;
            }
        }
        determinant += product;
        p.next();
    }
    return determinant;
}

Matrix Matrix::inverse() const{
    assert( determinant() != 0 );
    Matrix A(*this);
    Matrix E(_n, Type::ID);
    // Gauß-Jordan down
    for( int i = 0; i < _n-1; i++ ){
        E.divideRow(i, A(i,i));
        A.divideRow(i, A(i,i));
        for( int j = i+1; j < _n; j++ ){
            E.subRow(j, 1, i, A(j,i)/A(i, i));
            A.subRow(j, 1, i, A(j,i)/A(i, i));
        }
    }
    // Gauß-Jordan up
    for( int i = _n-1; i > 0; i-- ){
        E.divideRow(i, A(i,i));
        A.divideRow(i, A(i,i));
        for( int j = i-1; j >= 0; j-- ){
            E.subRow(j, 1, i, A(j,i)/A(i, i));
            A.subRow(j, 1, i, A(j,i)/A(i, i));
        }
    }
    return E;
}

double det(const Matrix& A){
    return A.determinant();
}

Matrix Matrix::addRow(int row1, int row2){
    for( int i = 0; i < _m; i++ ){
        (*this)(row1, i) += (*this)(row2, i);
    }
    return (*this);
}
Matrix Matrix::addRow(int row1, double f1, int row2, double f2){
    for( int i = 0; i < _m; i++ ){
        (*this)(row1, i) = (*this)(row1, i) * f1 + (*this)(row2, i) * f2;
    }
    return (*this);
}
Matrix Matrix::subRow(int row1, int row2){
    for( int i = 0; i < _m; i++ ){
        (*this)(row1, i) -= (*this)(row2, i);
    }
    return (*this);
}
Matrix Matrix::subRow(int row1, double f1, int row2, double f2){
    for( int i = 0; i < _m; i++ ){
        (*this)(row1, i) = (*this)(row1, i) * f1 - (*this)(row2, i) * f2;
    }
    return (*this);
}
Matrix Matrix::addCol(int col1, int col2){
    for( int i = 0; i < _n; i++ ){
        (*this)(i, col1) += (*this)(i, col2);
    }
    return (*this);
}
Matrix Matrix::addCol(int col1, double f1, int col2, double f2){
    for( int i = 0; i < _n; i++ ){
        (*this)(i, col1) = (*this)(i, col1)*f1 + (*this)(i, col2)*f2;
    }
    return (*this);
}

Matrix Matrix::subCol(int col1, double f1, int col2, double f2){
    for( int i = 0; i < _n; i++ ){
        (*this)(i, col1) = (*this)(i, col1)*f1 - (*this)(i, col2)*f2;
    }
    return (*this);
}

Matrix Matrix::subCol(int col1, int col2){
    for( int i = 0; i < _n; i++ ){
        (*this)(i, col1) -= (*this)(i, col2);
    }
    return (*this);
}

Matrix Matrix::multiplyRow(int row, double factor){
    assert( row >= 0 && row < _m );
    for( int i = 0; i < _n; i++ ){
        (*this)(row, i) *= factor;
    }
    return (*this);
}
Matrix Matrix::divideRow(int row, double factor){
    assert( row >= 0 && row < _m );
    for( int i = 0; i < _n; i++ ){
        (*this)(row, i) /= factor;
    }
    return (*this);
}
Matrix Matrix::multiplyCol(int col, double factor){
    assert( col >= 0 && col < _m );
    for( int i = 0; i < _m; i++ ){
        (*this)(i, col) *= factor;
    }
    return (*this);
}
Matrix Matrix::divideCol(int col, double factor){
    assert( col >= 0 && col < _m );
    for( int i = 0; i < _m; i++ ){
        (*this)(i, col) /= factor;
    }
    return (*this);
}

Matrix& Matrix::operator+=(const Matrix& A){
    assert(_m == A._m && _n == A._n);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            (*this)(i, j) += A(i,j);
        }
    }
    return *this;
}
Matrix& Matrix::operator-=(const Matrix& A){
    assert(_m == A._m && _n == A._n);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            (*this)(i, j) -= A(i,j);
        }
    }
    return *this;
}

Matrix& Matrix::operator=(const Matrix& A){
    delete _matrix;
    _m = A._m;
    _n = A._n;
    _matrix = new double*[_m];
    for( int i = 0; i < _m; i++ ){
        _matrix[i] = new double[_n];
        for( int j = 0; j < _n; j++ ){
            _matrix[i][j] = A(i, j);
        }
    }
    return *this;
}

Matrix operator+(const Matrix& A, const Matrix& B){
    assert( A._m == B._m && A._n == B._n );
    Matrix C(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            C(i,j) += B(i,j);
        }
    }
    return C;
}

Matrix operator-(const Matrix& A, const Matrix& B){
    assert( A._m == B._m && A._n == B._n );
    Matrix C(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            C(i,j) = A(i,j)-B(i,j);
        }
    }
    return C;
}

Matrix& Matrix::operator*=(double d){
    for( int i = 0; i < _m; i++ ){
        for( int j = 0; j < _n; j++ ){
            (*this)(i, j) *= d;
        }
    }
    return *this;
}

Matrix operator*(const Matrix& A, double d){
    Matrix B(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            B(i, j) *= d;
        }
    }
    return B;
}

Matrix operator*(double d, const Matrix& A ){
    Matrix B(A);
    for( int i = 0; i < A._m; i++ ){
        for( int j = 0; j < A._n; j++ ){
            B(i, j) *= d;
        }
    }
    return B;
}

Matrix operator*(const Matrix& A, const Matrix& B){
        assert(A._n == B._m);
        Matrix C(A._m, B._n);
        for( int i = 0; i < A._m; i++ ){
            for( int j = 0; j < B._m; j++ ){
                for( int k = 0; k < B._n; k++ ){
                    C(i, k) += A(i, j)*B(j, k);
                }
            }
        }
        return C;
}

double& Matrix::operator()(int i, int j){
    assert(i >= 0 && i < _m);
    assert(j >= 0 && j < _n);
    return _matrix[i][j];
}

double Matrix::operator()(int i, int j) const{
    assert(i >= 0 && i < _m);
    assert(j >= 0 && j < _n);
    return _matrix[i][j];
}


std::ostream& operator<<(std::ostream& out, const Matrix& A){
    for( int i = 0; i < A._m; i++ ){
        out << "( ";
        for( int j = 0; j < A._n; j++ ){
            if( j > 0 ){
                out << "\t";
            }
            out << A(i, j);
        }
        out << ")" << std::endl;
    }
    return out;
}

C++:
//Main.cpp
#include <iostream>
#include <typeinfo>
#include "Matrix.h"

using namespace std;

int main(){
    Matrix A(4, 4);
    int arr[4][4] = {{1, 2, 3, 4}, {4, 2, 4, 1}, {3, 2, 4, 4}, {3, 1, 1, 2}};
    for( int i = 0; i < 4; i++ ){
        for( int j = 0; j < 4; j++ ){
            A(i,j) = arr[i][j];
        }
    }
    cout << "Matrix A:" << endl;
    cout << A << endl;
    cout << "Determinante: " << det(A) << endl << endl;
    cout << "Matrix A^-1:"<< endl;
    cout << A.inverse() << endl;
    cout << "A x A^-1: " << endl;
    cout << A * A.inverse() << endl;
    return 0;
}

Das liefert dann die Ausgabe im Anhang :)

Augenscheinlich macht da die maschineninterne Präzision einen Strich durch die Rechnung. Gibt es da einen schöneren Weg als Runden oder vergleichen?
 

Anhänge

  • matrix.png
    matrix.png
    5,1 KB · Aufrufe: 22
Freut mich, dass es geholfen hat :)

Skalarmultiplikation: Das hab ich doch glatt übersehen, das der andere Parameter ein Einzelwert ist :D

Die Sache mit den R-Value-Referenzen + Move semantics: Ist auch nicht so wichtig, funktioniert so auch
(bzw. nicht wirklich immer, aber Fälle wo es wirklich nötig ist sind nicht soo häufig).
Worum es im Wesentliche geht, am Beispiel vom Copyconstructor:
Der bekommt ja ein Objekt, das Speicher reserviert und Werte drin hat, reserviert für sich selbst auch was und kopiert die Werte. Soweit ists ja in Ordnung. Wenn man jetzt einen Fall hat, wo man damit ein neues Objekt anlegt, aber das alte Objekt (von dem kopiert wird) danach nicht mehr verwendet, wäre es für Geschwindigkeit und Speicherverbrauch besser, dass das alte Objekt seinen reservierten Speicherblock einfach dem neue Objekt "schenkt" (das alte Objekt brauchts ja nicht mehr), und so nichts neues reserviert und herumkopiert werden muss.
Für Fälle, wo die Vorlage dann nicht mehr gebraucht wird, denkt man vermutlich zuerst dass man ja einfach das alte Objekt weiterverwenden kann, statt es zu kopieren und dann das alte nicht mehr zu verwenden. Aber zB. im Zusammenhang mit Funktionsparametern (erste Art, wo kopiert wird), kann das schon Sinn machen.
Seit C++11 gibts dafür diverse Syntaxsachen in die Sprache eingebaut, unter anderem eine weitere Referenzart usw.,



Zu der Präzisionssache: Einen schöneren Weg als Runden kenn ich da leider auch nicht.
 
Zuletzt bearbeitet:
Zurück