Matrices Introducing Inheritance

advertisement
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.
Download