============= Konstruktoren ============= Bislang erfolgte die Einrichtung einer Matrix außerhalb der `struct`: ---- CODE (type=cpp) ---------------------------------------------------------- Matrix A; A.m = 7; A.n = 8; A.data = new double[A.m * A.n]; A.incRow = 1; A.incCol = 7; ------------------------------------------------------------------------------- Dies ist nicht sehr schön und lädt gerade bei einer wiederholten Einrichtung zu Fehlern ein. Glücklicherweise lässt sich die Einrichtung eines Objekts einem sogenannten Konstruktor überlassen. Konstruktoren sehen wie Methoden aus, haben aber keinen Rückgabetyp und werden nach der `struct` (bzw. nach der Klasse) benannt: ---- CODE (type=cpp) ---------------------------------------------------------- struct Matrix { std::size_t m; /* number of rows */ std::size_t n; /* number of columns */ std::size_t incRow; std::size_t incCol; double* data; Matrix(std::size_t m_, std::size_t n_, std::size_t incRow_, std::size_t incCol_) { m = m_; n = n_; incRow = incRow_; incCol = incC_; data = new double[m*n]; } /* ... */ }; ------------------------------------------------------------------------------- Der Konstruktor wird dann bei der Variablendeklaration aufgerufen: ---- CODE (type=cpp) ---------------------------------------------------------- Matrix A(7, 8, 1, 7); ------------------------------------------------------------------------------- Wenn ein Konstruktor gegeben ist, muss dieser auch verwendet werden, d.h. das Anlegen einer Matrix ohne die Angabe all der Parameter ist nicht mehr zulässig. Da die Komponenten eines Objekts unter Umständen ebenfalls mit Parametern konstruiert werden müssen, reichen Zuweisungen nicht aus, da zu dem Zeitpunkt einer Zuweisung die jeweilige Komponente bereits konstruiert worden ist. Um dieses Problem zu lösen, können in C++ bei einem Konstruktor sogenannte _initializer_ verwendet werden. Syntaktisch werden die zwischen der Parameterliste und der öffnenden geschweiften Klammer untergebracht mit einem führenden Doppelpunkt: ---- CODE (type=cpp) ---------------------------------------------------------- struct Matrix { std::size_t m; /* number of rows */ std::size_t n; /* number of columns */ std::size_t incRow; std::size_t incCol; double* data; Matrix(std::size_t m, std::size_t n, std::size_t incRow, std::size_t incCol) : m(m), n(n), incRow(incRow), incCol(incCol), data(new double[m*n]) { } /* ... */ }; ------------------------------------------------------------------------------- Die einzelnen Komponenten sind in der Reihenfolge zu konstruieren, wie sie innerhalb der `struct` deklariert sind. Auch die elementaren Datentypen wie beispielsweise `std::size_t` oder `double*` werden in C++ wie Objekte behandelt, die einen Konstruktor anbieten mit einem Parameter des gleichen Typs, der zur Initialisierung verwendet wird, einem sogenannten Kopierkonstruktor (oder _copy constructor_). Es ist seit C++11 ausdrücklich zulässig, die Parameter eines Konstruktors gleich zu benennen wie die entsprechenden Komponenten. Wenn bei der Parameterübergabe in einem _initializer_ eines Konstruktors nach einem Namen gesucht wird, haben die Parameternamen Vorrang vor den Namen der Komponenten des Objekts. Diese Vorgehensweise ermöglicht noch eine weitere Technik. Wenn wir nicht wollen, dass die Dimensionierung einer Matrix und ihre Speicherorganisation im Laufe der Lebenszeit eines Objekts verändert werden, können wir diese mit `const` deklarieren und damit versehentliche Änderungen unterbinden: ---- CODE (type=cpp) ---------------------------------------------------------- struct Matrix { const std::size_t m; /* number of rows */ const std::size_t n; /* number of columns */ const std::size_t incRow; const std::size_t incCol; double* data; Matrix(std::size_t m, std::size_t n, std::size_t incRow, std::size_t incCol) : m(m), n(n), incRow(incRow), incCol(incCol), data(new double[m*n]) { } /* ... */ }; ------------------------------------------------------------------------------- Aufgabe ======= Da es bei einer vollbesetzten Matrix nur zwei naheliegenden Varianten gibt, die Daten zu organisieren (_row major_ und _column major_), liegt es nahe, den Konstruktor dahingehend zu vereinfachen, dass `incRow` und `incCol` nicht mehr explizit spezifiziert werden, sondern stattdessen nur _row major_ oder _column major_ ausgewählt wird und entsprechend `incRow` und `incCol` berechnet werden. Dazu lässt sich ein Aufzählungstyp `StorageOrder` definieren: ---- CODE (type=cpp) ---------------------------------------------------------- enum class StorageOrder {ColMajor, RowMajor}; ------------------------------------------------------------------------------- Dann haben wir den Typ `StorageOrder` mit den zulässigen Werten `StorageOrder::ColMajor` und `StorageOrder::RowMajor`. Passen Sie den Konstruktor in der folgenden Vorlage entsprechend an. Hilfreich ist dabei die Verwendung des `?:`-Operators in C++. Der Ausdruck `condition? expr1: expr2` liefert `expr1`, wenn die die Bedingung `condition` wahr ist, ansonsten `expr2`. Vorlage ======= :import: session7/matrix_class3.cpp :navigate: up -> doc:index back -> doc:session7/page04 next -> doc:session7/page06