|
|
Summary
• Matrix Class
• Definition of Matrix Constructor
• Destructor of Matrix Class
• Utility Functions of Matrix
• Input Function
• Transpose Function
• Code of the Program
Matrix Class
After talking at length about the concept of matrices in the previous lecture, we
are going
to have a review of ‘code’ today with special emphasis on concepts of constructors
and
destructors. We may also briefly discuss where should a programmer return by value,
return by reference besides having a cursory look on the usage of the pointers.
The data structure of the Matrix
class is very simple. We have defined an arbitrary
number of functions and operators. You may add and subtract more functions in it.
In this
lecture, we have chosen just a few as it is not possible to discuss each and every
one in a
brief discourse. As discussed earlier, the code is available to use that can be
compiled and
run. This class is not complete and a lot of things can be added to it. You should
try to
enhance it, try to improve and add to its functionality.
One of the things that a programmer will prefer to do with this class is its templatization.
We have implemented it for type
double. It was done due
to the fact that the elements of
the Matrix are of
type double. You
may like to improve and make it a templatized class
so that it can also handle integer elements. This class is written in a very straightforward
manner. The double
is used only for elements to develop it into a
Matrix class for
integers if you replace all the
double word with the
int. Be careful,
you cannot revert it
Page 562
back to double by
just changing all the int
to double
as integers are used other than
element types.
Let’s discuss the code beginning with the data structure of the class. In keeping
the
concepts of data hiding and encapsulation, we have put the data in the private section
of
the class. We
have defined number of rows (i.e.
numRows) and number of
columns (i.e.
numCols) as integers. These will always be whole number. As we cannot
have one and a
half row or column, so these are integers.
The private part of the Matrix
class is:
int numRows, numCols;
double **elements;
These are fixed and defined. So whenever you have an object of this class, it will
have a
certain number of rows and certain number of columns. These are stored in the variablesnumRows
and numCols. Where
will be the values of the elements of this matrix? The
next line is double **elements;
i.e. elements
is an array of pointers to
double. First * is
for array and second * makes it a pointer. It means that ‘elements’
is pointing to a twodimension
array of double.
When we say elements[i],
it means that it is pointing to an
array of double.
If we say elements[i][j],
it means we are talking about a particular
element of type double.
We have not taken the two-dimension array in the usual way but
going to dynamic memory allocation. We have developed a general
Matrix class. The
objects created from it i.e. the instances of this class, could be small matrices
as 2*2.
These may also be as big matrix as 20*20. In other words, the size of the matrix
is
variable. In fact, there is no requirement that size should be square. It may not
be 20*20.
It may be a matrix of 3*10 i.e. three rows and ten columns. So we have complete
flexibility. When we create an object, it will store the number of rows in
numRows and
number of columns in numCols.
The elements will be dynamically allocated memory in
which the double
value is stored.
While using the dynamic memory, it is good to keep in mind that certain things are
necessary to be implemented in the class. 1) Its constructor should make memory
allocation. We will use the
new operator, necessitating the need of defining its destructor
also. Otherwise, whenever we create an object, the memory will be allocated from
the
free store and not de-allocated resulting in the wastage of the memory. Therefore
a
destructor is necessary while de-allocating memory. 2) The other thing while dealing
with classes having dynamic memory allocation, we need to define an assignment
operator. If we do not define the assignment operator, the default will do the member
wise copy i.e. the shallow copy. The value of pointer will be copied to the pointer
but the
complete data will not be copied. We will see in the code how can we overcome this
problem..
Let’s discuss the code in the public interface of the class. The first portion of
the public
interface is as:
Page 563
Matrix(int=0, int=0); // default constructor
Matrix(const Matrix & ); // copy constructor
~Matrix(); // Destructor
In the public interface, the first thing we have is the constructors. In the default
constructor, you see the number of columns and number of rows. The default values
are
zero. If you just declare a matrix as
Matrix m, it will be an
object of class Matrix
having
zero rows and zero columns i.e. memory is not allocated yet. Then we have also written
a
copy constructor. Copy constructor becomes necessary while dealing with dynamic
memory allocation in the class. So we have to provide constructor, destructor, assignment
operator and the copy constructor. The declaration line of copy constructor is always
the
same as the name of the class. In the argument list, we have a reference to an object
of
the same class i.e. Matrix(const
Matrix & ); The
& represents the reference. This is the
prototype. After constructors, we have defined a destructor. Its prototype is also
standard.
Here it is ~Matrix();
it takes no argument and returns nothing. Remember that
constructors and destructors return nothing.
After this, we have utility functions for the manipulation of the
Matrix.
int getRows(void) const; // Utility fn, returns no. of rows
int getCols(void) const; // Utility fn, returns no. of columns
const Matrix & input(istream &is = cin); // Read from istream i.e. keyboard
const Matrix & input(ifstream &is); // Read matrix from ifstream
void output(ofstream &os) const; // Utility fn, prints matrix with graphics
void output(ostream &os = cout) const; // Utility fn, prints matrix with graphics
const Matrix& transpose(void); // Transpose the matrix and return a ref
We have defined two small functions as
getRows and
getCols which will return
the
number of rows and number of columns of the matrix respectively. Here, you are writing
the class and not the client which will use this class. During the usage of this
class, there
may be need of some more functions. You may need to add some more functionality
depending on its usage. At this point, a function may be written which will return
some
particular row as a vector or the nth column of a matrix. These things are left
for you to
do.
We need some function to input values into the matrix. There are two input functions
both named as input.
One is used to get the value from the keyboard while the other to
get the values from some file. There is a little bit difference between the declaration
of
these two that will be discussed later. The
input function which will
get the input from
the keyboard, takes an argument of type
istream. Remember that
cin, that is associated
with the keyboard, is of type
istream. These are member functions and called by some
object of Matrix.
The Matrix object
will be available to it through
this pointer. The
Page 564
istream is passed as argument and we have given a temporary name to the
input argument
i.e. is and its
default value is cin.
It is a nice way of handling default values. If you write
in the main program as m.input()
where m
is an object of type
Matrix, it will get the input
from the keyboard. This is due to the fact that it is getting
cin by default. We have
defined another input
function and that is an example of function overloading. This
function takes ifstream
as input argument i.e. a file stream. If we want to read the matrix
data from some file, this input
function may be employed. There is no default argument
in it.
Similarly we have two output
functions. The names are chosen as arbitrary. You may
want to use print or display. One output function will display the matrix on the
screen. Its
argument is ostream
while the default value will be
cout. The other output
function will
be used when we want to write the matrix in some file. It takes
ofstream as argument and
we have not provided any default argument to it. You will have to provide a file
handle to
use this function.
Let’s continue to talk about the arithmetic manipulations we want to do with the
matrices.
Matrix operator+( Matrix &m) const; // Member op + for A+B; returns matrix
Matrix operator + (double d) const; // Member op + for A+d; returns matrix
const Matrix & operator += (Matrix &m); // Member op += for A +=B
friend Matrix operator + (double d, Matrix &m); // friend operator for d+A
Matrix operator-( Matrix & m) const; // Member op - for A-B; returns matrix
Matrix operator - (double d) const; // Member op - for A-d;
const Matrix & operator -= (Matrix &m); // Member op -= for A-=B;
friend Matrix operator - (double d, Matrix& m); // Friend op - for d-A;
Matrix operator*(const Matrix & m); // Member op * for A*B;
Matrix operator * (double d) const; // Member op * for A*d;
friend Matrix operator * (const double d, const Matrix& m);//friend op*, d*A
const Matrix& transpose(void); // Transpose the matrix and return a ref
const Matrix & operator = (const Matrix &m); // Assignment operator
We have defined different functions for plus. Some of these are member operators
while
the others called as friend. The first plus operator is to add two matrices. It
is a member
operator that takes a Matrix
object as argument. The second one is to add some
double
number to matrix. Remember that we are having a class of
Matrix with
double elements.
So we will add double
number to it. It is also a member operator. When you write
something like m + d,
where m is an object
of type Matrix and
d is a
double variable, this
operator will be called. Here
Matrix object is coming on the left-hand side and will be
available inside the operator definition by this pointer. On the other hand, the
double
value d is presented
as argument to the operator.
Page 565
There is another variety of adding double to the matrix i.e. if we write as
d + m. On the
left-hand side, we don’t have
Matrix. It cannot be a member function as the driving force
is not an object of our desired class. If not a member function, it will have two
arguments.
First argument will be of type
double while the second
one is going to be of Matrix
object
in which we will add the double
number. As it is not a member function and we want to
manipulate the private data members of the class, it has to be a friend function.
Function
will be defined outside, at file level scope. In other words, it will not be a member
function of class Matrix
but declared here as a friend. It is defined as:
Friend Matrix operator + (double d, Matrix &m);
Its return type is Matrix.
When we add a double
number, it will remain as
Matrix .We
will be able to return it. The final variant is included as an example of code reuse
i.e. the
+= operator. We write in our program
i += 3; and it means
i = i + 3; It would
be nice if
we can write A += B
where A and
B both are matrices.
After plus, we can do the same thing with the minus operator. Having two matrices-A
and
B, we want to do
A-B. On the left hand side, we have
Matrix, so it will be a
member
operator. The other matrix will be passed as argument. In
A-B, A is calling this
operator
while B being passed
as argument. We can also do
A-d where A
is a Matrix
and d is
of
type double. For this, we will have to write a member operator. All these operators
are
overloaded and capable of returning
Matrix. In this overloaded
operator, double
will be
passed as argument. Then we might want to do it as
d - A, where
d is
double variable and
A is of type Matrix.
Since the left hand side of the minus (-) is
double, we will need a
friend function, as it is not possible to employ a member function. Its prototype
is as:
friend Matrix operator - (double d, Matrix& m);
Let’s now talk about multiplication. We have discussed somewhat about it in the
previous
lecture. For multiplication, the first thing one needs to do is the multiplication
of two
matrices i.e. A*B
where A and
B both are matrices.
As on the left hand side of the
operator * we have a Matrix
so it will be a member function so
A can be accessed through
this pointer. B
will be passed as argument. A matrix should be returned. Thus, we have
a
member operator that takes an argument of type
Matrix and returns a
Matrix. You may
like to do the same thing, which we did with the plus and minus. In other words,
a
programmer will multiply a matrix with a
double or multiply a
double with a matrix.
In
either case, we want that a matrix should be returned. So at first,
A * d should be a
member function. Whereas d
* A will be a friend function. Again the return type will be a
Matrix.
In case of division, we have only one case i.e. the division of the matrix with
a double
number. This is A / d
where A is
a Matrix while
d is a
double variable. We will
divide all
the elements of the matrix with this
double number. This will
return a matrix of the same
size as of original.
Page 566
Taking benefit of the stream insertion and extraction operator, we can use double
greater
than sign ( >>) and write as
>> m where m
is a Matrix.
For this purpose, we have written
stream extraction operator. The insertion and extraction functions will be friend
functions
as on the left-hand side, there will be either input stream or output stream.
We have also defined assignment function in it. As we are using dynamic memory
allocation in the class, assignment is an important operator. Another important
function is
transpose, in which we will interchange the rows into columns and return a
Matrix. This
is the interface of our Matrix
class. You may want to add other functions and operators
like +=, -=, *=. But it is not possible to add /= due to its very limited scope.
We have used the keyword const in our class. You will find somewhere the return
type as
Matrix and somewhere as
Matrix &. We will discuss
each of these while dealing with
the code in detail.
Definition of Matrix Constructor
Let’s start with the default constructor. Its prototype is as under:
Matrix(int = 0, int = 0); // default constructor
We are using the default argument values here. In the definition of this function,
we will
not repeat the default values. Default values are given at one place. The definition
code is
as:
Matrix::Matrix(int row, int col) //default constructor
{
numRows = row;
numCols = col;
elements = new (double *) [numRows];
for (int i = 0; i < numRows; i++){
elements[i] = new double [ numCols];
for(int j = 0; j < numCols; j++)
elements[i][j] =0.0; // Initialize to zero
}
}
Two integers are passed to it. One represents the number of rows while the other
is
related to the number of columns of the
Matrix object that we want
to create. At first,
we will assign these values to
numRows and
numCols that form a part
of the data
structure of our class. Look at the next line. We have declared
elements as
**elements i.e.
the array of pointers to double.
We can directly allocate it by getting the elements of
Page 567
numRows times numCols
of type double
from free store. But we don’t have the
double
pointer. Therefore, first of all, we say that element is array of pointer to
double and
allocate pointers as much as needed. So we use the
new operator and use
double* cast. It
means that whatever is returned is of type pointer to
double. How many pointers
we
need? This is equal to number of rows in the matrix. There is one pointer for each
of the
rows i.e. now a row can be an array. This is a favorable condition, as we have to
enter
data in the columns. Now elements
is numRows
number of pointers to
double. After
having this allocation we will run a loop.
In C/C++ languages, every row starts from zero and ends with
upper-limit – 1. Now in
the loop, we have to allocate the space for every
elements[i]; How much space
is needed
here? This will be of type
double and equal to number of columns. So when we say
new
double[numCols], it means an array of
double. As
elements[i] represents
a pointer and
now it is pointing to an array. Remember that pointers and arrays are synonymous.
Now
a pointer is pointing to this array. We have the space now and want to initialize
the
matrix. For this, we have written another loop. To assign the value, we will write
as:
elements[i][j] = 0.0;
Let’s review this again. First of all we have assigned the values to
numRows and
numCols. Later, we allocated the pointers of
double from the free store
and assigned to
elements. Then we got the space for each of this pointer to store
double. Finally,
we
initialized this space with 0.0. Now we have a constructive matrix.
Is there any exceptional value? We can think of assigning negative values to number
of
rows or number of columns. If you want to make sure that this does not happen, you
can
put a test by saying that numRows
and numCols
must be greater than or equal to zero.
Passing zero is not a problem as we have zero space and nothing happens actually.
Now
we get an empty matrix of dimension 0*0. But any positive number supplied will give
us
a constructor and initialize zero matrix.
Let’s discuss the other constructor. This is more important in the view of this
class
especially at a time when we are going to do dynamic memory allocation. This is
copy
constructor. It is used when we write in our program as
Matrix A(B); where
A and
B both
are matrices. We are going to construct
A while
B already exists. Here
we are saying that
give us a new object called
A which should be identical to the already existing object
B.
So it is a copy constructor. We are constructing an object as a copy of another
one that
already exists. The other usage of this copy constructor is writing in the main
function as
Matrix A = B; Remember that this is declaration and not assignment statement.
Here
again copy constructor will be called. We are saying that give us a duplicate of
B and its
name should be A.
Here is the code of this function:
Matrix::Matrix(const Matrix &m)
{
numRows = m.numRows;
numCols = m.numCols;
Page 568
elements = new (double *) [numRows];
for (int i = 0; i < numRows; i++){
elements[i] = new double [ numCols];
for(int j = 0; j < numCols; j++)
elements[i][j] = m.elements[i][j];
}
}
We are passing it a constant reference of
Matrix object to ensure
that the matrix to be
copied is not being changed. Therefore we make it
const. We are going to
create a brand
new object that presently does not exist. We need to repeat the code of regular
constructor except the initialization part. Its rows will be equal to the rows of
the object
supplied i.e. numRows = m.numRows.
Similarly the columns i.e.
numCols= m.numCols.
Now we have to allocate space while using the same technique earlier employed in
case
of the regular constructor. In the default constructor, we initialize the elements
with zero.
Here we will not initialize it with zero and assign it the value of
Matrix m as
elements[i][j] = m.elements[i][j]. Remember that we use the dot operator
to access the
data members. We have not used the dot operator on the left hand side. This is due
to the
fact that this object is being constructed and available in this function through
this
pointer. Therefore the dot operator on the left hand side is not needed. We will
use it on
the right hand side to access the data members of
Matrix m. This is our copy
constructor.
In this function, we have taken the number of rows and columns of the object whose
copy
is being made. Then we allocate it the space and copy the values of elements one
by one.
The other thing that you might want to know is the use of nested loop both in regular
constructor and the copy constructor.
Destructor of Matrix Class
‘Destructor’ is relatively simple. It becomes necessary after the use of new in
the
constructor. While creating objects, a programmer gets memory from the free store.
So in
the destructor, we have to return it. We will do it as:
delete [] elements;
Remember that ‘elements’
is the variable where the memory has been allocated. The []
simply, indicates that it is an array. The compiler automatically takes care of
the size of
the array and the memory allocated after being returned, goes back to the free store.
There is only one line in the destructor. It is very simple but necessary in this
case.
Utility Functions of Matrix
The functions getRows()
and getCols()
are relatively simple. They do not change
anything in the object but only read from the object. Therefore we have made this
function constant by writing the
const keyword in the end.
It means that it does not
change anything. The code of the
getRows() functions is
as follows:
int Matrix :: getRows ( ) const
{
Page 569
return numRows;
}
This function returns an int
representing the number of rows. It will be used in the main
function as i = m.getRows();
where i
is an int
and m is
a Matrix object.
Same thing
applies to the getCols()
function. It is of type
const and returns an
int representing
the
number of columns.
Let’s talk about little bit more complicated function. It is the output to the screen
functions. We want that our matrix should be displayed on the screen in a beautiful
way.
You have seen that in the books that matrix is written in big square brackets. The
code of
the function is as:
void Matrix::output(ostream &os) const
{
// Print first row with special characters
os.setf(ios::showpoint);
os.setf(ios::fixed,ios::floatfield);
os << (char) 218;
for(int j = 0; j < numCols; j++)
os << setw(10) << " ";
os << (char) 191 << "\n";
// Print remaining rows with vertical bars only
for (int i = 0; i < numRows; i++){
os << (char) 179;
for(int j = 0; j < numCols; j++)
os << setw(10)<< setprecision(2) << elements[i][j];
os << (char) 179 << "\n";
}
// Print last row with special characters
os << (char) 192;
for(int j = 0; j < numCols; j++)
os << setw(10) << " ";
os << (char) 217 << "\n";
}
We have used special characters that can be viewed in the command window. We have
given you an exercise of printing the ASCII characters on the screen. After ASCII
code
128, we have special graphic symbols. We have used the values of those symbols here.
To print those in the symbol form, we have forced it to be printed as
char. If we do not
use the char, it
would have printed the number 218 i.e. it would have written an integer.
Page 570
We have forced it to print the character whose ASCII value is 218. Now it prints
the
graphic symbol for that character. We have referenced the ASCII table and seen which
symbol will fit in the left corner i.e. 218. So we have written it as:
os << (char) 218;
os is the output stream.
char is forcing it to be
print as character. The left corner will be
printed on the screen as . Now we
need the space to print the columns of the matrix. In
the first line we will print the spaces using a loop as:
for(int j = 0; j < numCols; j++)
os << setw(10) << " ";
Here we have changed the width as 10. You can change it to whatever you like. Then
we
print nothing in the space of ten characters and repeat that for the number of columns
in
the matrix. After this, we printed the right corner. This is the first line of the
display.
Other lines will also contain the values of the elements of the matrix. These lines
will
start with a vertical line and then the element values of the row and in the end
we have a
vertical line. For each row, we have a vertical bar and the number values which
will be
equal to number of columns (elements in each row equals to the number of columns)
and
then a vertical bar in the end. In the beginning of this code, we have used two
other
utilities to improve the formatting. Here we have a matrix of type
double so every
element of the matrix is double.
For double, we have
used
os.setf(ios::fixed,ios::floatfield); that means that it is a fixed display.
Scientific notation
will not be used while decimal number is displayed with the decimal point. After
this, we
have set the precision with two number of places. So we have a format and our decimal
numbers will always be printed with two decimal places. The numbers are being printed
with a width of ten characters so the last three places will be as
.xx. Rest of the code is
simple enough. We have used the nested loops. Whenever you have to use rows and
columns, it will be good to use nested loops. When all the rows have been printed,
we
will print the below corners. We referenced the ASCII table, got the graphic symbol,
printed it, left the enough space and then printed the other corner. The matrix
is now
complete. When this is displayed on the screen, it seems nicely formatted matrix
with
graphic symbols. That is our basic output function.
Let’s look at the file output function. While doing the output on the screen, we
made it
nicely formatted. Now you may like to store the matrix in a file. While storing
the matrix
in the file, there is no need of these lines and graphic symbol. We only need its
values to
read the matrix from the file. So there is a pair of functions i.e. output the matrix
in the
file and input from the file. To write the output function, we actually have to
think about
the input function.
Suppose, we have declared a 2*2
Matrix m in our program.
Somewhere in the program,
we want to populate this matrix from the file. Do we know that we have a 2*2 matrix
in
the file. How do we know that? It may 5*5 or 7*3 matrix. So what we need to do is
somehow save the number of rows and columns in the file as well. So the output function
Page 571
that puts out on the file must put out the number of rows and number of columns
and then
all of the elements of the matrix. Following is the code of this function:
void Matrix::output(ofstream &os) const
{
os.setf(ios::showpoint);
os.setf(ios::fixed,ios::floatfield);
os << numRows << " " << numCols << "\n";
for (int i = 0; i < numRows; i++){
for(int j = 0; j < numCols; j++)
os << setw(6) << setprecision(2) << elements[i][j];
os << "\n";
}
}
The code is shorter than the other output function due to non-use of the graphical
symbols. First of all, we output the number of rows and number of columns. Then
for
these rows and columns, data elements are written. We have also carried out a little
bit
formatting. While seeing this file in the notepad, you will notice that there is
an extra line
on the top that depicts the number of rows and columns.
Input Functions
Input functions are also of two types like output functions. The first function
takes input
from the keyboard while the other takes input from the file. The function that takes
input
from the keyboard is written in a polite manner because humans are interacting with
it.
We will display at the screen ”Input Matrix size: 3 rows by 3 columns” and it will
ask
“Please enter 3 values separated by spaces for row no. 1” for each row. Spaces are
delimiter in C++. So spaces will behave as pressing enter from the keyboard. If
you have
to enter four numbers in a row, you will enter as number (space) number (space)
number
(space) number before pressing the enter key. We have a loop inside which will process
input stream and storing these values into
elements[i][j]; So the
difference between this
function and the file input function is 1) It prompts to the user and is polite.
2) It will read
from the keyboard and consider spaces as delimiter.
The other input function reads from the file. We have also stored the number of
rows and
number of columns in the file. The code of this function is:
const Matrix & Matrix::input(ifstream &is)
{
int Rows, Cols;
is >> Rows;
is >> Cols;
Page 572
if(Rows > 0 && Cols > 0){
Matrix temp(Rows, Cols);
*this = temp;
for(int i = 0; i < numRows; i++){
for(int j = 0; j < numCols; j++){
is >> elements[i][j];
}
}
}
return *this;
}
First of all, we will read the number of rows and number of columns from the file.
We
have put some intelligence in it. It is better to check whether
numRows and
numCols is
greater than zero. If it is so, then do something. Otherwise, there is nothing to
do. If rows
and columns are greater than zero, then there will be a temporary matrix specifying
its
rows and columns. These values are read from the file, showing that we have a matrix
of
correct size. Now this matrix is already initialized to zero by our default constructor.
We
can do two things. We can either read the matrix, return the value or we can first
assign it
to the matrix that was calling this function. We have assigned it first as
*this = temp; here
temp is a temporary matrix which is created in this function but
*this is whatever
this
points to. Remember that this is a member function so
this pointer points to
the matrix
that is calling this function. All we have to do is to assign the
temp to the matrix, which
is
calling this function. This equal to sign is our assignment operator, which we have
defined in our Matrix
class. If the dimensions of the calling matrix are not equal to the
temp matrix, the assignment operator will correct the dimensions of the
calling matrix. It
will assign the values, which in this case is zero so far. Now we will read the
values from
the file using the nested loops. The other way is to read the values from the file
and
populate the temp
matrix before assigning it to the calling matrix in the end. That is the
end of the function. Remember that the
temp matrix, which we have
declared in this
function, will be destroyed after the exit from the function. This shows that the
assignment operator is important here. All the values will be copied and it will
perform a
deep copy. Does this function return something? Its return type is reference to
a const
Matrix. Its ending line is return
*this that means return
whatever this points
to and it is
returned as reference. The rule of thumb is whenever we are returning the
this pointer, it
will be returned as a reference because this is the same object which is calling
it. When
you are returning a matrix that is not a reference, it is a returned by value. The
complete
matrix will be copied on the stack and returned. This is slightly wasteful. Yet
you cannot
return a reference to the temp
object in this code. The reference of the
temp will be
returned but destroyed when the function is finished. The reference will be pointing
to
nothing. So you have to be careful while returning a reference to
this.
Transpose Function
The transpose of a matrix will interchange rows into columns. There are two alternative
requirements. In the first case, we have a square matrix i.e. the number of rows
is equal to
Page 573
number of columns. In this situation, we don’t need extra storage to do this. If
the number
of rows is not equal to the number of columns, then we have to deal it in a different
way.
We can use general case for both purposes but you will notice that it is slightly
insufficient. Here is the code of the function.
const Matrix & Matrix::transpose()
{
if(numRows == numCols){ // Square matrix
double temp;
for(int i = 0;i < numRows; i++){
for(int j = i+1; j < numCols; j++){
temp = elements[i][j];
elements[i][j] = elements[j][i];
elements[j][i] = temp;
}
}
}
else // not a square matrix
{
Matrix temp(numCols, numRows);
for(int i = 0; i < numRows; i++){
for(int j = 0; j < numCols; j++){
temp.elements[j][i] = elements[i][j];
}
}
*this = temp;
}
return *this;
}
In the beginning, we checked the case of square matrix i.e. if the number of rows
is equal
to number of columns. Here we are dealing with the square matrix. We have to change
the rows into columns. For this purpose, we need a temporary variable. In this case,
it is a
variable of type double
because we are talking about a
double matrix. Look at
the loop
conditions carefully. The outer loop runs for
i = 0 to
i < numRows and the inner
loop
runs from j = i+1
to j < numCols.
Then we have standard swap functionality. We have
processed one triangle of the matrix. If you start the inner loop from zero, think
logically
what will happen. You will interchange a number again and again, but nothing will
happen in the end, leaving no change in the matrix. This is the case of the square
matrix.
But in case of non-square matrix i.e. the code in the else part, we have to define
a new
matrix. Its rows will be equal to the columns of the calling matrix and its columns
will be
equal to the number of rows. So we have defined a new
Matrix temp with the number
of
rows and columns interchanged as compared to the calling matrix. Its code is
straightforward. We are doing the element to element copy. The difference is, in
the loop
we are placing the x row, y
col element of the calling matrix to
y row, x col of the
temp
matrix. It is an interchange of the rows and columns according to the definition
of the
Page 574
transpose. When we have all the values copied in the
temp. We do our little
magic that is
*this = temp. Which means whatever
this points to, now assigned
the values of the matrix
temp. Now our horizontal matrix becomes vertical and vice versa. In the
end, we return
this. This is the basic essence of transpose code.
We will continue the discussion on the code in the next lecture. We will look at
the
assignment operator, stream operator and try to recap the complete course.
Code of the Program
The complete code of the matrix class is:
#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <stdio.h>
#include <fstream.h>
class Matrix
{
private:
int numRows, numCols;
double **elements;
public:
Matrix(int=0, int=0); // default constructor
Matrix(const Matrix & ); // copy constructor
~Matrix(); // Destructor
int getRows(void) const; // Utility fn, returns no. of rows
int getCols(void) const; // Utility fn, returns no. of columns
const Matrix & input(istream &is = cin); // Read matrix from istream
const Matrix & input(ifstream &is); // Read matrix from istream
void output(ofstream &os) const; // Utility fn, prints matrix with graphics
void output(ostream &os = cout) const; // Utility fn, prints matrix with graphics
const Matrix& transpose(void); // Transpose the matrix and return a ref
const Matrix & operator = (const Matrix &m); // Assignment operator
Matrix operator+( Matrix &m) const; // Member op + for A+B; returns matrix
Matrix operator + (double d) const;
const Matrix & operator += (Matrix &m);
friend Matrix operator + (double d, Matrix &m);
Matrix operator-( Matrix & m) const; // Member op + for A+B; returns matrix
Matrix operator - (double d) const;
const Matrix & operator -= (Matrix &m);
Page 575
friend Matrix operator - (double d, Matrix& m);
Matrix operator*(const Matrix & m);
Matrix operator * (double d) const;
friend Matrix operator * (const double d, const Matrix& m);
Matrix operator/(const double d);
friend ostream & operator << ( ostream & , Matrix & );
friend istream & operator >> ( istream & , Matrix & );
friend ofstream & operator << ( ofstream & , Matrix & );
friend ifstream & operator >> ( ifstream & , Matrix & );
};
Matrix::Matrix(int row, int col) //default constructor
{
numRows = row;
numCols = col;
elements = new (double *) [numRows];
for (int i = 0; i < numRows; i++){
elements[i] = new double [ numCols];
for(int j = 0; j < numCols; j++)
elements[i][j] = 0; // Initialize to zero
}
}
Matrix::Matrix(const Matrix &m)
{
numRows = m.numRows;
numCols = m.numCols;
elements = new (double *) [numRows];
for (int i = 0; i < numRows; i++){
elements[i] = new double [ numCols];
for(int j = 0; j < numCols; j++)
elements[i][j] = m.elements[i][j];
}
}
Matrix::~Matrix(void)
{
delete [] elements;
}
int Matrix :: getRows ( ) const
{
return numRows;
Page 576
}
int Matrix :: getCols ( ) const
{
return numCols;
}
void Matrix::output(ostream &os) const
{
// Print first row with special characters
os.setf(ios::showpoint);
os.setf(ios::fixed,ios::floatfield);
os << (char) 218;
for(int j=0; j<numCols; j++)
os << setw(10) << " ";
os << (char) 191 << "\n";
// Print remaining rows with vertical bars only
for (int i=0; i<numRows; i++){
os << (char) 179;
for(int j=0; j<numCols; j++)
os << setw(10)<< setprecision(2) << elements[i][j];
os << (char) 179 << "\n";
}
// Print last row with special characters
os << (char) 192;
for(int j=0; j<numCols; j++)
os << setw(10) << " ";
os << (char) 217 << "\n";
}
void Matrix::output(ofstream &os) const
{
os.setf(ios::showpoint);
os.setf(ios::fixed,ios::floatfield);
os << numRows << " " << numCols << "\n";
for (int i=0; i<numRows; i++){
for(int j=0; j<numCols; j++)
os << setw(6) << setprecision(2) << elements[i][j];
os << "\n";
}
}
const Matrix & Matrix::input(istream &is)
{
cout << "Input Matrix size: " << numRows << " rows by " << numCols << "
Page 577
columns\n";
for(int i=0; i<numRows; i++){
cout << "Please enter " << numCols << " values separated by spaces for row
no." << i+1 << ": ";
for(int j=0; j<numCols; j++){
cin >> elements[i][j];
}
}
return *this;
}
const Matrix & Matrix::input(ifstream &is)
{
int Rows, Cols;
is >> Rows;
is >> Cols;
if(Rows>0 && Cols > 0){
Matrix temp(Rows, Cols);
*this = temp;
for(int i=0; i<numRows; i++){
for(int j=0; j<numCols; j++){
is >> elements[i][j];
}
}
}
return *this;
}
const Matrix & Matrix::transpose()
{
if(numRows == numCols){ // Square matrix
double temp;
for(int i=0; i<numRows; i++){
for(int j=i+1; j<numCols; j++){
temp = elements[i][j];
elements[i][j] = elements[j][i];
elements[j][i] = temp;
}
}
}
else
{
Matrix temp(numCols, numRows);
for(int i=0; i<numRows; i++){
for(int j=0; j<numCols; j++){
temp.elements[j][i] = elements[i][j];
Page 578
}
}
*this = temp;
}
return *this;
}
const Matrix & Matrix :: operator = ( const Matrix & m )
{
if( &m != this){
if (numRows != m.numRows || numCols != m.numCols){
delete [] elements;
elements = new (double *) [m.numRows];
for (int i = 0; i < m.numRows; i++)
elements[i]=new double[m.numCols ];
}
numRows = m.numRows;
numCols = m.numCols;
for ( int i=0; i<numRows; i++){
for(int j=0; j<numCols; j++){
elements[i][j] = m.elements[i][j];
}
}
}
return *this;
}
Matrix Matrix::operator + ( Matrix &m ) const
{
// Check for conformability
if(numRows == m.numRows && numCols == m.numCols){
Matrix temp(m);
for (int i = 0; i < numRows; i++){
for (int j = 0; j < numCols; j++){
temp.elements[i][j] += elements[i][j];
}
}
return temp ;
}
}
Matrix Matrix::operator + ( double d ) const
{
Matrix temp(*this);
for (int i = 0; i < numRows; i++){
for (int j = 0; j < numCols; j++){
Page 579
temp.elements[i][j] += d;
}
}
return temp ;
}
const Matrix & Matrix::operator += (Matrix &m)
{
*this = *this + m;
return *this;
}
Matrix Matrix::operator - ( Matrix &m ) const
{
// Check for conformability
if(numRows == m.numRows && numCols == m.numCols){
Matrix temp(*this);
for (int i = 0; i < numRows; i++){
for (int j = 0; j < numCols; j++){
temp.elements[i][j] -= m.elements[i][j];
}
}
return temp ;
}
}
Matrix Matrix::operator - ( double d ) const
{
Matrix temp(*this);
for (int i = 0; i < numRows; i++){
for (int j = 0; j < numCols; j++){
temp.elements[i][j] -= d;
}
}
return temp ;
}
const Matrix & Matrix::operator -= (Matrix &m)
{
*this = *this - m;
return *this;
}
Matrix Matrix::operator* ( const Matrix& m)
{
Matrix temp(numRows,m.numCols);
Page 580
if(numCols == m.numRows){
for ( int i = 0; i < numRows; i++){
for ( int j = 0; j < m.numCols; j++){
temp.elements[i][j] = 0.0;
for( int k = 0; k < numCols; k++){
temp.elements[i][j] += elements[i][k] * m.elements[k][j];
}
}
}
}
return temp;
}
Matrix Matrix :: operator * ( double d) const
{
Matrix temp(*this);
for ( int i = 0; i < numRows; i++){
for (int j = 0; j < numCols; j++){
temp.elements[i][j] *= d;
}
}
return temp;
}
Matrix operator * (const double d, const Matrix& m)
{
Matrix temp(m);
temp = temp * d;
return temp;
}
Matrix Matrix::operator / (const double d)
{
Matrix temp(*this);
for(int i=0; i< numRows; i++){
for(int j=0; j<numCols; j++){
temp.elements[i][j] /= d;
}
}
return temp;
}
Matrix operator + (double d, Matrix &m)
{
Matrix temp(m);
for(int i=0; i< temp.numRows; i++){
Page 581
for(int j=0; j<temp.numCols; j++){
temp.elements[i][j] *= d;
}
}
return temp;
}
Matrix operator - (double d, Matrix& m)
{
Matrix temp(m);
for(int i=0; i< temp.numRows; i++){
for(int j=0; j<temp.numCols; j++){
temp.elements[i][j] = d - temp.elements[i][j];
}
}
return temp;
}
ostream & operator << ( ostream & os, Matrix & m)
{
m.output();
return os;
}
istream & operator >> ( istream & is, Matrix & m)
{
m.input(is);
return is;
}
ofstream & operator << ( ofstream & os, Matrix & m)
{
m.output(os);
return os;
}
ifstream & operator >> ( ifstream & is, Matrix & m)
{
m.input(is);
return is;
}
int main()
{
// declaring two matrices
Matrix m(4,5), n(5,4);
Page 582
// getting input from keyboard
cout << "Taking the input for m(4,5) and n(5,4) \n";
m.input();
n.input();
// displaying m and taking its transpose
cout << "Displaying the matrix m(4,5) and n(5,4)\n";
m.output();
n.output();
cout << "Taking the transpose of matrix m(4,5) \n";
m.transpose();
cout << "Displaying the matrix m(5,4) and n(5,4) \n";
m.output();
cout << "Adding matrices n into m \n";
m = m + n;
m.output();
cout << "Calling m + m + 4 \n";
m = m + m + 4;
m.output();
cout << "Calling m += n \n";
m += n;
m.output();
cout << "Calling m = m - n \n";
m = m - n;
m.output();
cout << "Calling m = m - 4 \n";
m = m - 4;
m.output();
cout << "Calling m -= n \n";
m -= n;
m.output();
m.transpose();
Matrix c;
cout << "Calling c = m * n \n";
Page 583
c = m * n;
c.output();
cout << "Calling c = c * 4.0 \n";
c = c * 4.0;
c.output();
cout << "Calling c = 4.0 * c \n";
c = 4.0 * c ;
c.output();
cout << "Testing stream extraction \n";
// cin >> c;
cout << "Testing stream insertion \n";
// cout << c;
cout << "Writing into the file d:\\junk.txt \n" ;
ofstream fo("D:/junk.txt");
fo << c;
fo.close();
cout << "Reading from the file d:\\junk.txt \n";
ifstream fi("D:/junk.txt");
fi >> c;
fi.close();
cout << c;
system("PAUSE");
return 0;
}
The output of the program is:
Taking the input for m(4,5) and n(5,4)
Input Matrix size: 4 rows by 5 columns
Please enter 5 values separated by spaces for row no.1: 1.0 2.0 3.0 4.0 5.0
Please enter 5 values separated by spaces for row no.2: 7.0 5.5 2.3 2.0 1.0
Please enter 5 values separated by spaces for row no.3: 3.3 2.2 1.1 4.4 5.5
Please enter 5 values separated by spaces for row no.4: 9.9 5.7 4.3 2.3 1.5
Input Matrix size: 5 rows by 4 columns
Please enter 4 values separated by spaces for row no.1: 11.25 12.25 13.25 14.25
Please enter 4 values separated by spaces for row no.2: 25.25 50.50 75.75 25.50
Please enter 4 values separated by spaces for row no.3: 15.15 5.75 9.99 19.90
Page 584
Please enter 4 values separated by spaces for row no.4: 25.50 75.75 10.25 23.40
Please enter 4 values separated by spaces for row no.5: 50.50 75.50 25.25 15.33
Displaying the matrix m(4,5) and n(5,4)
┌ ┐
│ 1.00 2.00 3.00 4.00 5.00│
│ 7.00 5.50 2.30 2.00 1.00│
│ 3.30 2.20 1.10 4.40 5.50│
│ 9.90 5.70 4.30 2.30 1.50│
└ ┘
┌ ┐
│ 11.25 12.25 13.25 14.25│
│ 25.25 50.50 75.75 25.50│
│ 15.15 5.75 9.99 19.90 │
│ 25.50 75.75 10.25 23.40│
│ 50.50 75.50 25.25 15.33│
└ ┘
Taking the transpose of matrix m(4,5)
Displaying the matrix m(5,4) and n(5,4)
┌ ┐
│ 1.00 7.00 3.30 9.90│
│ 2.00 5.50 2.20 5.70│
│ 3.00 2.30 1.10 4.30│
│ 4.00 2.00 4.40 2.30│
│ 5.00 1.00 5.50 1.50│
└ ┘
Adding matrices n into m
┌ ┐
│ 12.25 19.25 16.55 24.15│
│ 27.25 56.00 77.95 31.20│
│ 18.15 8.05 11.09 24.20 │
│ 29.50 77.75 14.65 25.70│
│ 55.50 76.50 30.75 16.83│
└ ┘
Calling m + m + 4
┌ ┐
│ 28.50 42.50 37.10 52.30 │
│ 58.50 116.00 159.90 66.40│
│ 40.30 20.10 26.18 52.40 │
│ 63.00 159.50 33.30 55.40 │
│ 115.00 157.00 65.50 37.66│
└ ┘
Calling m += n
┌ ┐
│ 39.75 54.75 50.35 66.55 │
│ 83.75 166.50 235.65 91.90│
│ 55.45 25.85 36.17 72.30 │
Page 585
│ 88.50 235.25 43.55 78.80 │
│ 165.50 232.50 90.75 52.99│
└ ┘
Calling m = m - n
┌ ┐
│ 28.50 42.50 37.10 52.30 │
│ 58.50 116.00 159.90 66.40│
│ 40.30 20.10 26.18 52.40 │
│ 63.00 159.50 33.30 55.40 │
│ 115.00 157.00 65.50 37.66│
└ ┘
Calling m = m - 4
┌ ┐
│ 24.50 38.50 33.10 48.30 │
│ 54.50 112.00 155.90 62.40│
│ 36.30 16.10 22.18 48.40 │
│ 59.00 155.50 29.30 51.40 │
│ 111.00 153.00 61.50 33.66│
└ ┘
Calling m -= n
┌ ┐
│ 13.25 26.25 19.85 34.05 │
│ 29.25 61.50 80.15 36.90 │
│ 21.15 10.35 12.19 28.50 │
│ 33.50 79.75 19.05 28.00 │
│ 60.50 77.50 36.25 18.33 │
└ ┘
Calling c = m * n
┌ ┐
│ 5117.55 8866.42 4473.54 3066.94 │
│ 7952.36 15379.14 7884.15 5202.50 │
│ 4748.18 8540.74 7566.73 3570.75 │
│ 3386.23 5949.35 4280.89 2929.51 │
└ ┘
Calling c = c * 4.0
┌ ┐
│ 20470.19 35465.70 17894.15 12267.75 │
│ 31809.46 61516.55 31536.59 20810.01 │
│ 18992.71 34162.97 30266.91 14283.00 │
│ 13544.91 23797.41 17123.54 11718.05 │
└ ┘
Calling c = 4.0 * c
┌ ┐
│ 81880.76 141862.80 71576.62 49071.00 │
│ 127237.84 246066.20 126146.34 83240.04 │
│ 75970.86 136651.88 121067.65 57132.02 │
Page 586
│ 54179.64 95189.64 68494.16 46872.18 │
└ ┘
Testing stream extraction
Testing stream insertion
Writing into the file d:\junk.txt
Reading from the file d:\junk.txt
┌ ┐
│ 81880.76 0.81 0.62 0.00 │
│ 127237.84 0.20 0.35 0.04 │
│ 75970.86 0.88 0.66 0.02 │
│ 54179.65 0.65 0.16 0.18 │
└ ┘
Press any key to continue . . . |
|
|
|