
















Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
An implementation of a matrix class in c++, including constructor, utility functions, and various matrix operations such as addition, subtraction, multiplication, and transpose. The code includes member functions, friend functions, and operator overloading.
Typology: Study notes
1 / 24
This page cannot be seen from the preview
Don't miss anything!

















Lecture 25 - Lecture 43
Matrix Class Definition of Matrix Constructor Destructor of Matrix Class Utility Functions of Matrix Input Function Transpose Function Code of the Program
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 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 variables- numRows 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 two-dimension 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 22. These may also be as big matrix as 2020. 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 2020. It may be a matrix of 310 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:
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
docsity.com
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 AB; Matrix operator * (double d) const; // Member op * for Ad; 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.
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.
docsity.com
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.
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.
docsity.com
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; 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
docsity.com
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 { 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";
docsity.com
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 22 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 22 matrix in the file. How do we know that? It may 55 or 73 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 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
docsity.com
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; 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
docsity.com
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 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
docsity.com
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); 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;
docsity.com
cout << "Input Matrix size: " << numRows << " rows by " << numCols << " 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]; } } *this = temp;
docsity.com
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++){ temp.elements[i][j] += d; } } return temp ; }
const Matrix & Matrix::operator += (Matrix &m)
docsity.com
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++){ 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]; } }
docsity.com
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);
// 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;
docsity.com