Representing a matrix using a struct

Content

In our previous programs, matrices were specified using a series of parameters:

void
initMatrix(size_t m, size_t n, double* A,
      ptrdiff_t incRowA, ptrdiff_t incColA) {
   /* ... */
}

In C (and C++ as well) these parameters can be joined to a composite type, a so-called struct:

struct Matrix {
   size_t m;
   size_t n;
   double* A;
   ptrdiff_t incRowA;
   ptrdiff_t incColA;
};

As it does not appear to be meaningful to associate the name A in general with a matrix, it is preferable to use a more generic name like data:

struct Matrix {
   size_t m;
   size_t n;
   double* data;
   ptrdiff_t incRow;
   ptrdiff_t incCol;
};

Note that we use size_t as unsigned integer type suitable for the dimensions, and ptrdiff_t as signed integer type for offsets which can also be negative. Both types, size_t and ptrdiff_t have a size that depends on the architecture and the currently selected model. When compiled for 32-bit (using -m32), both are 32-bit integers, when compiled for 64-bit (using -m64), both will be 64-bit integers.

In C++ we have namespaces and both types, size_t and ptrdiff_t, belong to the namespace std, i.e. we have to use std::size_t and std::ptrdiff_t.

#include <cstddef>

struct Matrix {
   std::size_t m;
   std::size_t n;
   std::ptrdiff_t incRow;
   std::ptrdiff_t incCol;
   double* data;
};

C++ is based on C and allows the types and functions of the C standard library to be used. However, different header names are to be used. The C++ specific headers do not flood the global name space (as in C) but put all standard identifiers into the std namespace. For example, where you had #include <stddef.h> in C, you use #include <cstddef> in C++. You could likewise use std::printf from #include <cstdio>. We provide, however, a type-safe alternative implementation of printf, named fmt::printf which is available through #include <printf.hpp>. You can download fmt::printf from github if you do not have it on your machine.

C++ is a so-called object-oriented programming language which allows to associate functions with a struct (or a class). These functions are called methods. A method named init which is similar to the initMatrix function we have seen before could be added as follows:

struct Matrix {
   std::size_t m;
   std::size_t n;
   std::ptrdiff_t incRow;
   std::ptrdiff_t incCol;
   double* data;

   void init() {
      for (std::size_t i = 0; i < m; ++i) {
         for (std::size_t j = 0; j < n; ++j) {
            data[i*incRow + j*incCol] = j * m + i + 1;
         }
      }
   }
};

Using this struct, you could initialize matrices using the method we have added above:

Matrix A;
A.m = 7; A.n = 8;
A.data = new double[A.m * A.n];
A.incRow = 1; A.incCol = 7;
A.init();

Each method call consists of an expression that designates an object (A in this example), the name of the method (init in this case), and a list of parameters (empty in this example). The referenced object is passed as an implicit parameter. Whenever within the init method one of the struct variables is accessed (like data), we access the corresponding component of the object which was implicitly passed when the method was invoked.

Exercise

Turn the function print_matrix in the following example into a method of the Matrix struct named print.

Template

#include <cstddef> /* needed for std::size_t and std::ptrdiff_t */
#include <printf.hpp> /* needed for fmt::printf */

struct Matrix {
   std::size_t m; /* number of rows */
   std::size_t n; /* number of columns */
   std::ptrdiff_t incRow;
   std::ptrdiff_t incCol;
   double* data;

   void init() {
      for (std::size_t i = 0; i < m; ++i) {
         for (std::size_t j = 0; j < n; ++j) {
            data[i*incRow + j*incCol] = j * m + i + 1;
         }
      }
   }
};

void print_matrix(std::size_t m, std::size_t n,
      const double* A, std::ptrdiff_t incRowA, std::ptrdiff_t incColA) {
   for (std::size_t i = 0; i < m; ++i) {
      fmt::printf("  ");
      for (std::size_t j = 0; j < n; ++j) {
         fmt::printf(" %4.1lf", A[i*incRowA + j*incColA]);
      }
      fmt::printf("\n");
   }
}

int main() {
   Matrix A;
   A.m = 7; A.n = 8;
   A.data = new double[A.m * A.n];
   A.incRow = 1; A.incCol = 7;
   A.init();
   fmt::printf("A =\n");
   print_matrix(A.m, A.n, A.data, A.incRow, A.incCol);
   delete[] A.data;
}

Compiling and executing

theon$ g++ -std=gnu++11 -o matrix_class1 matrix_class1.cpp
theon$ ./matrix_class1
A =
    1.0  8.0 15.0 22.0 29.0 36.0 43.0 50.0
    2.0  9.0 16.0 23.0 30.0 37.0 44.0 51.0
    3.0 10.0 17.0 24.0 31.0 38.0 45.0 52.0
    4.0 11.0 18.0 25.0 32.0 39.0 46.0 53.0
    5.0 12.0 19.0 26.0 33.0 40.0 47.0 54.0
    6.0 13.0 20.0 27.0 34.0 41.0 48.0 55.0
    7.0 14.0 21.0 28.0 35.0 42.0 49.0 56.0
theon$