Reputation:
I was writing code on class Matrix. So I have a small difficulty in understanding how constructor is used. Actually I have particular doubt on default constructor and parameterized constructor.
Default constructor of class : Matrix() Initialise rows and columns and matrix elements zero.
Parameterized constructor: Matrix(int rows, int columns) initialise the values passed, and 2D matrix elements with default value 0.
I don't know how both constructors work. Don't class has only one constructor, or can it have more than one constructor. I know how to write default constructor and how to write parameterized constructor. And help with some on how this both constructors work when we write both in same class. Will this work?
class Matrix{
private:
int rows;
int columns;
int **mat;
public:
Matrix(int row, int column){
this->rows = row;
this->columns = column;
mat = new int *[row];
for(int i=0;i<rows;i++){
mat[i]=new int[column];
}
}
};
Upvotes: 0
Views: 1748
Reputation: 122142
There is much wrong in your code. You cannot use rows
and columns
as array size when they are only known at runtime. Even if you could use row
and columns
as array size, you are using them as size of the array before you assign any value to them. Moreover this->mat[rows][columns]={0};
tries to access one element that is out of bounds of the array, it invokes undefined behavior. Use a std::vector
for dynamically sized arrays.
Yes a class can have more than one constructor. Which constructor gets called is decided by overload resolution. In the example that follows, the constructor to be called can be simply determined by the number of parameters passed. In general overload resolution is more complicated (and beyond the scope of this answer).
#include <vector>
#include <iostream>
struct Matrix {
int rows;
int columns;
std::vector<std::vector<int>> data;
Matrix() : rows(0),columns(0) {}
Matrix(int rows,int columns) : rows(0),columns(0),data(rows,std::vector<int>(columns)) {}
};
int main() {
Matrix m1;
std::cout << m1.rows() << "\n";
std::cout << m1.columns() << "\n";
Matrix m2{5,10};
std::cout << m2.rows() << "\n";
std::cout << m2.columns() << "\n";
}
Note that std::vector
also has more than one constructor: https://en.cppreference.com/w/cpp/container/vector/vector. Matrix()
uses the vectors default constructor (1) to create an empty vector. data(rows,std::vector<int>(columns))
initializes data
with a vector of vectors, by calling the vector constructor that takes a size and value (3).
The term "parametrized constructor" is a misnomer. The distinction between a "parametrized constructor" and a default constructor is wrong and misleading. A constructor can be parametrized and a default constructor at the same time. A default constructor is a constructor that can be called without parameters. This can be because it has no arguments or because it has default arguments. For example the two above can be equivalently written as one. Moreoever you do not need to store rows
and columns
as members, because the vector can tell you its size via its size()
method:
#include <vector>
#include <iostream>
struct Matrix {
std::vector<std::vector<int>> data;
Matrix(int rows=0,int columns=0) : data(rows,std::vector<int>(columns)) {}
size_t rows() { return data.size(); }
size_t columns() {
if (data.size()) return data[0].size();
return 0;
}
};
int main() {
Matrix m1;
std::cout << m1.data.size() << "\n";
Matrix m2{5,10};
std::cout << m2.data.size() << "\n";
std::cout << m2.data[0].size() << "\n";
}
Here Matrix(int rows=0,int columns=0)
is a default constructor and it is parametrized, because it can be called with either of the two:
Matrix m1;
Matrix m2{5,10};
However, the constructor with default parameters can also be called via
Matrix m3{42};
and this may not be desirable. Hence the better alternative is perhaps (as mentioned by Caleth):
struct Matrix {
std::vector<std::vector<int>> data;
Matrix(int rows,int columns) : rows(0),columns(0),data(rows,std::vector<int>(columns)) {}
Matrix() : Matrix(0,0) {}
};
This uses a delegating constructor to avoid repeating some code (available since C+11).
PS: A vector of vectors isnt a particularly good data structure. The strenght of std::vector
is locality of its data, but that gets lost in a std::vector<std::vector<int>>
. The int
s in a std::vector<int>
are stored in contiguous memory. But the int
s in a std::vector<std::vector<int>>
are not all stored in contiguous memory. That is because the elements are not stored within the vector. Often it is better to use a flat std::vector<int>
also for the 2D case and emulate the second dimension by index transformations.
Upvotes: 3
Reputation: 25516
There are quite a number of more fundamental problems in your code:
int rows;
int columns;
int mat[rows][columns];
is an illegal definition of a dynamic array in C++. Array sizes need to be compile time constants in C++, but rows
and columns
are not (and that wouldn't even change if you made both of const
, as different instances of the class might use different values.
Even if it was legal, in your default constructor you would access the arrays out of bounds in your constructor:
mat[rows][columns] = ...
With array sizes of 0 (which again are illegal in C++) there is no accessible index 0 in arrays.
So before we can go on we first need to fix these issues. There are several options for, depending on your needs.
A pretty simple approach is maintaining the data in a std::vector
of std::vectors
:
class Matrix
{
std::vector<std::vector<int>> mat;
public:
// ...
};
Vectors implicitly store information about their internal sizes, so the former rows
and columns
members (for which the correct types would have been size_t
anyway, not int
) are redundant and can and should get dropped in favour of functions using the information from the vectors:
size_t rows() { return mat.size(); }
size_t columns() { return mat.size() == 0 ? 0 : mat[0].size(); }
I assume here all rows being of same size, if not (i.e. a jagged array) we couldn't apply that for the columns.
The vector of vector is a really convenient way to implement a dynamic matrix, however comes with the cost of double indirection on matrix element access, need of an additional array (transparent to you, but you need it for storing the rows) and having to allocate the columns individually (again transparent to you).
You get more efficient if you implement your matrix based on a 1D-array:
size_t m_columns;
std::vector<int> mat;
Now you need to store columns separately, and you need to calculate the correct offsets into the matrix explicitly:
size_t rows() { return mat.size() / m_columns; }
size_t columns() { return m_columns; }
int& at(size_t x, size_t y) { return mat[x * m_columns + y]; }
Note that you still can provide the m[x][y]
syntax, replacing the at
function, but that gets quite a bit more complicated so I'll leave that out for now. Advantages, though, are single indirection and thus faster access, no need of additional memory and one single allocation for all memory, thus faster again.
Yet another approach is providing constant array sizes in form of template parameters:
template<size_t Rows, size_t Columns>
class Matrix
{
int mat[Rows][Columns];
};
This comes with fastest access to array elements and highest type safety, as you can provide operators for transposing, addition, multiplication, ... in a way such that you cannot pass matrices of bad dimensions to. On the other hand you lose much of flexibility, e.g. you cannot place matrices of different dimensions into the same container (vectors, lists or others) directly as templates with different parameters form different types (there are ways around, though, but less convenient, based on polymorphism) and need to specify sizes in the type already in code, i. e. you cannot calculate matrix sizes dynamically.
There are use cases for both approaches (based on dynamic allocation and on statically typed matrices), you need to select according to your concrete requirements. For a very first go I'd recommend the double vector approach for its simplicity, despite of its other disadvantages. Once it works you might switch to the 1D array approach...
Now about your actual problem:
You can always overload constructors, i.e. provide multiple ones that differ in their number and/or types of parameters; the most appropriate one will then be selected according to the arguments you provide (though in some specific cases ambiguities can arise).
In your case you'd provide:
Matrix();
Matrix(size_t rows, size_t columns); // not needed in the template variant,
// dimensions are given via
// template parameters
You should, though, use the constructor's initialiser list (not to be confused with std::initialiser list), which might look as follows:
Matrix() : /*rows(0), columns(0),*/ mat() { }
Remember that I dropped the members for rows and columns, thus they won't appear in the constructor any more either, I left them in comments to illustrate how it would have looked like if I hadn't. Note that mat()
calls the vector's default constructor which creates an empty one. Calling it explicitly is optional, you could have left it out, then it would have been called implicitly.
With std::vector
you are lucky: It provides a constructor with the number of elements to be initialised with as well as an optional default value that will be used for all elements; sowe can simply write:
Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }
That's it...
Your complete class then might look as follows (combining all the information from above):
class Matrix
{
std::vector<std::vector<int>> mat;
public:
Matrix() = default; // short hand syntax...
Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }
size_t rows() { return mat.size(); }
size_t columns() { return mat.size() == 0 ? 0 : mat[0].size(); }
// still providing `at` function as returning a reference to
// a row vector would allow to modify that one e. g. in size
// and thus create an invalid matrix!
int& at(size_t x, size_t y) { return mat[x][y]; }
};
You might stop at here for now, but actually we are not yet at the end. Once you get above working (please try to implement on your own, don't just copy/paste, you won't learn anything from), you can get back to here for the next step:
class Matrix
{
std::vector<std::vector<int>> mat;
public:
Matrix() = default; // short hand syntax...
Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }
size_t rows() const { return mat.size(); }
size_t columns() const { return mat.size() == 0 ? 0 : mat[0].size(); }
// still providing `at` function as returning a reference to
// a row vector would allow to modify that one e. g. in size
// and thus create an invalid matrix!
int at(size_t x, size_t y) const { return mat[x][y]; }
int& at(size_t x, size_t y) { return mat[x][y]; }
};
Have you noticed the const
keywords? They allow to access instances that are declared const
, i.e. unmodifiable. You should declare all member functions const
that do not modify the object.
For the at
function I provided both versions – the const one just returns a value, this way no (illegal) modification of the matrix is possible, while the non-const one reference such that the matrix element can be changed.
Overload resolution is pretty simple in such a case: const
overload on const
matrices, non-const
overload on non-const
matrices:
Matrix m(1, 1);
Matrix const& mr = m; // const reference!
m.at(0, 0); // non-const at
mr.at(0, 0); // const at
m.rows(); // can call const functions on non-const objects, if there's
// no non-const overload – but not the other way round
So far the basics. If you are yet interested in the m[x][y]
syntax for the Matrix
class leave a comment, I might add a few extra lines ;)
Upvotes: 0
Reputation: 1
Yes we can have more than one constructor for a given class. This is illustrated in the below given example.
Second note that in Standard C++ the size of an must be a compile time constant. So when you wrote:
int mat[rows][columns];//THIS IS NOT STANDARD C++
The above statement is not standard C++.
A better way would be to use a 2D std::vector
as shown below. You can use this example as a reference.
#include <iostream>
#include <vector>
class Matrix{
private:
std::size_t rows;
std::size_t columns;
//use a std::vector instead of array
std::vector<std::vector<int>> mat;
public:
//default constructor
Matrix(): rows(0), columns(0), mat() //this uses constructor initiailzer list
{
}
//parameterized constuctor
Matrix(std::size_t pRows, std::size_t pColumns): rows(pRows), columns(pColumns), mat(rows, std::vector<int>(columns))//this also uses constructor initiailzer list
{
}
//member function to display columns and rows of the matrix
void display()
{
for(auto &r: mat)
{
for(auto &element: r)
{
std::cout<<element<<" ";
}
std::cout<<std::endl;
}
std::cout<<"--------"<<std::endl;
}
};
int main()
{
Matrix m1; //this uses default constructor
m1.display();
Matrix m2(5,7);//this uses parameterized constructor
m2.display();
return 0;
}
Some of the modifications that i made include:
display()
function that prints out all the elements(rows and columns) inside the matrix.m1
that uses the default constructor to initialize its data members rows
, columns
and mat
.m2
that uses the parameterized constructor to initialize its data members rows
, columns
and mat
.Upvotes: 0
Reputation: 133
I would like to point you to any C++ book.
A class can have any number of constructors, given they all differ in signature (number, type and order of arguments).
Your class has one constructor, which is the default constructor. It can only create zero dimensional matrices.
Using this->
to access data members is optional and I don't think many developers use it actually.
I assume, you don't want to change the dimension of your matrix dynamically (hopefully), so I'd suggest to make the members rows
and columns
const
.
And, you have to realize: C++ is no interpreted language. It is compiled on one day and run at an other. The compiler has no idea whatsoever, which values will be used for rows
and columns
, so using these dimensions in the declaration of the members won't work. Most of the times the compiler will complain about non-const dimension of the array.
You have several possibilities: determine at run-time which dimensions to use or determine at compile-time. Each has its advantages and disadvantages. I guess for an absolute beginner, the run-time approach looks easier, but the compile-time approach is way faster.
class matrix_runtime
{
public:
matrix_runtime(int r, int c)
: rows(r), columns(c)
{
mat = std::make_unique<int[]>(rows*columns);
}
private:
int const rows{};
int const columns{};
std::unique_ptr<int[]> mat;
};
template<size_t Rows, size_t Columns>
class matrix_compiletime
{
public:
matrix_compiletime() = default;
public:
std::array<int, Rows * Colums> mat;
};
If you know at compile-time which dimensions to use, I strongly suggest to use the compile-time version. Matrices with different dimensions will be different types (that's the point with matrices, isn't it?), so the compiler will help you avoiding bugs by giving you errors, if you try.
Here is some documentation on the standard library stuff I used:
and if you want to learn about C++ initialization: https://en.cppreference.com/w/cpp/language/initialization
Upvotes: 0