Matrices Introducing Inheritance Consider A matrix is a grid in which numbers can be stored. 1 9 2 8 3 7 4 6 5 Algorithms for problems in scientific computing frequently store the coefficients for a system of equations in a matrix, for convenient manipulation. A class to represent matrices is thus a useful class for scientific computing. Problem Let’s write a program to perform matrix addition. 1 9 2 8 3 7 4 6 5 m1 + 1 2 3 4 5 6 7 8 9 m2 2 11 5 12 8 13 11 14 10 m3 For simplicity, we will store a matrix’s values in a file. Since there is no predefined Matrix type, we will design a Matrix class... Behavior Our program should explain its purpose, and prompt for and read the values of the first matrix from the keyboard. It should then do the same for the second matrix. It should then add the two matrices producing a third matrix. It should then display the third matrix to the screen. Objects Description 1st matrix keyboard 2cd matrix 3rd matrix screen Type Matrix istream Matrix Matrix ostream Kind Name varying varying varying varying varying matrix1 cin matrix2 matrix3 cout Operations Description Predefined? Library? Name display a string yes string << read a matrix from istream no --- >> add 2 matrices no --- ??? write a matrix to ostream no --- ??? Algorithm 0. 1. 2. 3. 4. Display purpose of program Prompt for and read matrix1 using cin. Prompt for and read matrix2 using cin. Compute matrix3 = matrix1 + matrix2. Write matrix3 to cout. Coding Our Algorithm Ideally, we want to implement our algorithm this way: // matAdd.cpp // ... documentation // ... other #includes #include “Matrix.h” int main() { cout << “\nTo add two matrices, enter the” << “\n values of the first matrix“ << “\n (one row per line)\n”; Matrix mat1; cin >> mat1; cout << “\nDo the same for the second matrix:\n”; Matrix mat2; cin >> mat2; // ... Coding (Ct’d) // ... matAdd.cpp continued Matrix mat3 = mat1 + mat2; cout << << << << “\nThe sum of\n” mat1 << “\n\n and\n\n” mat2 << “\n\n is\n\n” mat3 << “\n\n”; } If we build an easy-to-use Matrix class, writing a program to manipulate matrices can be as easy as writing a program that manipulates integers. Analysis We have a few function members to define! Our algorithms requires: – matrix input from an istream – matrix addition – matrix output to an ostream In addition, we should provide “normal” class operations (normal constructors, accessors) as well as other Matrix operations. Difficulty We could declare our Matrix class as follows: class Matrix { public: // ... private: int myRows, myColumns; typedef vector<double> Row; vector<Row> myGrid; }; However, if we do so, then we must redefine many of the vector operations for our Matrix class, which will greatly increase our development time. For Example If we take this approach, then we will be unable to use the subscript operator on a Matrix object: Matrix mat1; // ... cout << mat1[0][0] << endl; The reason is that a Matrix declared using this approach has-a vector of vectors as a data member, but such Matrix is not a vector of vectors. That is, although subscript is an operation defined for class vector, it is not defined for a Matrix unless we define it (as is the case for all vector operations). A Solution One way to avoid this problem is to declare our Matrix class as an extension to vector of vectors: typedef vector<double> Row; class Matrix : public vector<Row> { // ... }; This approach tells the compiler that a Matrix is a vector< vector<double> >. Since such a Matrix is a vector<Row>, any operation that can be applied to vector<Row> can be applied to a Matrix (including subscript)! Example With this definition (and a constructor we’ll write shortly), we can write: Matrix mat1(2,3); cin >> mat1[0][0]; mat1 [0] [1] [2] [0] [1] inherited data member(s) Example With this definition (and a constructor we’ll write shortly), we can write: Matrix mat1(2,3); cin >> mat1[0][0]; mat1 [0] [1] [2] [0] [1] inherited data member(s) The first subscript selects the Row whose index is 0 within mat1. Example With this definition (and a constructor we’ll write shortly), we can write: Matrix mat1(2,3); cin >> mat1[0][0]; mat1 [0] [1] [2] [0] [1] inherited data member(s) The first subscript selects the Row whose index is 0 within mat1. The second subscript selects the column whose index is 0 with in that Row. Inheritance In such a declaration, Matrix is said to be derived from vector<Row>. The Matrix class is said to inherit all of the members (data and function) of vector<Row>. The pattern for such a declaration is as follows: class ChildClass : public ParentClass { // ... }; ChildClass is derived from ParentClass, and inherits all of its members, both function and data. Inheritance (Ct’d) Parent Class The is-a relationship is often drawn like this: A parent class can have many derived classes, which are sometimes called child classes: Derived Class Parent Class ... Child1 Child2 ChildN Inheritance (Ct’d) Parent Class Child classes can also be parents, producing class hierarchies: ... Child1 GChild1 Child2 GChild2 Such hierarchies are useful for modeling relationships among real world objects. ChildN GChild3 Vehicle compact sedan Truck wagon ... Car ... By consolidating common code in parent classes, inheritance can eliminate all redundant code. Boat ... ... Using Inheritance Inheritance can be used to declare any class that is a special instance of another class. Since the derived class inherits all members of the parent class, inheritance should only be used if every operation on the parent class can be appropriately applied to the derived class Declaring Matrix We can thus start a Matrix class as follows: typedef vector<double> Row; class Matrix : public vector<Row> { public: private: int myRows, myColumns; }; No myGrid data member is needed, because Matrix inherits the implementation details of vector<Row>. The data members myRows and myColumns are not required, but they simplify some operations. The Matrix Interface class Matrix : public vector<Row> { public: Matrix(); Matrix(int rows, int columns); int Rows() const; int Columns() const; Matrix operator+(const Matrix & mat2) const; // ... other Matrix-specific operations void Read(istream & in); void Print(ostream & out) const; friend istream & operator>>(istream & in, Matrix & chart); friend ostream & operator<<(ostream & in, const Matrix & chart); private: // ... data members omitted }; Default Constructor The default constructor initializes the data members to default values: Matrix mat1; Specification: Postcondition: myRows == 0 && myColumns == 0. We should use the vector<Row> constructor to initialize the inherited data members... Default Constructor This is sufficiently simple to define inline in Student.h: inline Matrix::Matrix() : vector<Row>() { myRows = 0; myColumns = 0; } Default Constructor This is sufficiently simple to define inline in Student.h: inline Matrix::Matrix() : vector<Row>() { myRows = 0; myColumns = 0; } The notation : vector<Row>() calls the constructor for class vector<Row> (Matrix’s parent class). A derived class constructor can (and should) always use this pattern to call the constructor of its parent class, to initialize its inherited data members. Explicit-Value Constructor This constructor lets you construct a Matrix of a specified size (in rows and columns): Matrix mat1(3, 5); mat1 The inherited data are wrapped in orange. Specification: ??? [0] [1] [2] [3] [4] [0] 0 0 0 0 0 [1] 0 0 0 0 0 [2] 0 0 0 0 0 myRows myColumns 3 5 Receive: rows, columns, two int values. Precondition: rows > 0 && columns > 0. Postcondition: myRows == rows && myColumns == columns && I contain a 2-D vector of rows rows and columns columns. Explicit-Value Constructor This is sufficiently simple to define inline: inline Matrix:: Matrix(int rows, int columns) : vector<Row>(rows, Row(columns)) { assert(rows > 0 && columns > 0); myRows = rows; myColumns = columns; } Explicit-Value Constructor This is sufficiently simple to define inline: inline Matrix:: Matrix(int rows, int columns) : vector<Row>(rows, Row(columns)) { assert(rows > 0 && columns > 0); myRows = rows; myColumns = columns; } The : vector<Row>(rows, Row(columns)) calls vector<Row>() to initialize the inherited members. This constructor lets the caller specify the size (rows), and the initial value (Row(columns)) of the vector. The Row constructor (i.e., vector<double>) is used to define the initial value as a vector of size columns. Extractors The extractors retrieve data member values: cout << mat1.Rows() << mat1.Columns(); Specifications: Rows(): Columns(): Return myRows. Return myColumns. Extractors These are sufficiently simple to define inline: inline int Matrix::Rows() const { return myRows; } inline int Matrix::Columns() const { return myColumns; } Element Access Thanks to our having derived Matrix from vector<Row>, we can write: cout << mat1[r][c]; and access the element at row r, column c, using the inherited subscript operators. Element Access Thanks to our having derived Matrix from vector<Row>, we can write: cout << mat1[r][c]; and access the element at row r, column c, using the inherited subscript operators. Since mat1 is a vector<Row>, sending mat1 the subscript message [r] accesses the Row in mat1 whose index is r. Element Access Thanks to our having derived Matrix from vector<Row>, we can write: cout << mat1[r][c]; and access the element at row r, column c, using the inherited subscript operators. Since mat1 is a vector<Row>, sending mat1 the subscript message [r] accesses the Row in mat1 whose index is r. We then send that Row the subscript message [c], which accesses the column within that Row whose index is c. Print() This member lets you write a matrix to an ostream: mat1.Print(cout); Specification: Receive: out, an ostream. Output: my (Matrix) values, to out. Passback: out, containing my Matrix values; Defining Print() This is sufficiently complicated to define separately. // ... void Matrix::Print(ostream & out) const { for (int r = 0; r < Rows(); r++) for (int c = 0; c < Columns(); c++) { fout << (*this)[r][c]; if (c < Columns()-1) out << ‘\t’; else out << ‘\n’; } } // for each r // for each c // display // either // tab // or // newline this The tricky thing here is that within a function member, we must send ourselves the subscript message. Every C++ function member has a variable named this. When that member’s message is sent to an object, the address of the receiving object is stored in this. mat1.Print(cin); this Matrix::Print() mat1 this (Ct’d) In a C++ function member, this always contains the address of the object receiving the message. Since it stores an address, this can be thought of as pointing to the object receiving the message, and address-storing variables are commonly called pointers. Since the value of this is an address, we can’t use it as is to refer to the receiver of the message. this (Ct’d) When applied to a pointer as a prefix operator, the asterisk (*) produces as its value the object pointed to. That is, if we use the notation: (*this) within a function member, the effect will be to access the receiver of the message (i.e., ourselves). To send ourselves the subscript message, we thus write: (*this)[r][c] which selects the Row whose index is r within ourselves (and then sends that Row a second subscript message). Insertion Overloading the insertion operator will let us display a Matrix in the “normal” manner: cout << mat << endl; Since its left operand is an ostream, this function cannot be implemented as a function member. Specification: Receive: out, an ostream; mat, a Matrix. Output: the values in mat, via out. Passback: out, containing the Matrix. Return: out, for chaining. Defining Insertion Thanks to Print(), this is sufficiently simple to inline. // ... inline ostream & operator<<(ostream & out, const Matrix & mat) { mat.Print(out); // send mat the Print() msg return out; // allow chaining } We simply send our Matrix parameter the Print() message, and let it do the work... Read() This member lets you read a matrix via an istream: mat1.Read(cin); Specification: Receive: in, an istream. Precondition: in contains the values of an m-by-n matrix, with each row on a separate line. Input: the matrix values, via in. Passback: in, the matrix values extracted from it. Postcondition: I contain the input values. Defining Read() This is sufficiently complicated to define separately. // ... void Matrix::Read(istream & in) { double number; char separator; for (;;) // row-loop { Row aRow; // empty row for (;;) // column-loop { in >> number; // read number if (in.eof()) break; // quit if failed aRow.push_back(number); // append number in.get(separator); // read next char if (separator == ‘\n’) break;// quit if e-o-l } // end column-loop if (in.eof()) break; // quit if eof push_back(aRow); // append Row } // end row-loop } Extraction The extraction operator lets us read a Matrix from an istream, like any other object: cin >> mat1; Specification: Receive: in, an istream; mat, a Matrix. Precondition: myRows == m && myColumns == n && in contains the values of an m-by-n matrix, with one row/line. Input: the matrix, via in. Passback: in, the matrix read from it; mat, containing the extracted values. Return: in, for chaining. Defining Extraction Thanks to Read(), this is simple enough to inline: // ... inline istream & operator>>(istream & in, Matrix & mat) { mat.Read(in); return in; } We simply send our Matrix parameter the Read() message, and let it do the work... Matrix Addition Defining the + operator will let us add matrices: Matrix mat3 = mat1 + mat2; Since its left operand is a Matrix, we can define operator+ as a Matrix function member, in which case such an expression will be treated as: Matrix mat3 = mat1.operator+(mat2); Specification: Receive: mat2, a Matrix. Precondition: mat2.Rows() == myRows && mat2.Columns() == myColumns. Return: mat3, containing the sum of myself and mat2. Addition Operator This is sufficiently complicated to define separately. // ... Matrix Matrix::operator+(const Matrix & mat2) const { assert(mat2.Rows() == myRows && mat2.Columns() == myColumns); Matrix result(myRows, myColumns); for (int r = 0; r < myRows; r++) for (int c = 0; c < myColumns; c++) result[r][c] = (*this)[r][c] + mat2[r][c]; return result; } Since the problem requires that we access all of the values in a 2-D structure, we use two nested for loops. Our Program Our program will now work “as advertised”. // ... int main() { // ... cin >> mat1; // ... cin >> mat2; // ... Matrix mat3 = mat1 + mat2; // ... cout << mat3; // ... } All of our work is reuseable, and we can add more matrix-specific operations to class Matrix... Summary If a new class is a special instance of an existing class, derivation can be used to define the new class. A derived class inherits all members (except constructors and destructors) of its parent class. A derived class constructor should use the parent class constructor to initialize inherited data members. In function members, this is built-in variable containing the address of the receiver of the message. Within a function member, the expression (*this) refers to the object receiving the message.