В предыдущей статье было показано, как можно использовать одномерный массив в качестве двумерного. Одновременно был отмечен ряд недостатков такой реализации. Повторим их кратко:
В C++ существует возможность перегрузки операторов, которая позволяет задать собственное поведение операторов пользовательских типов. Т.к. переопределить операторы стандартных типов нельзя, то повлиять на поведение '[]' одномерного массива мы никак не можем. Поэтому, чтобу "подружить" одномерный массив с его интерпритацией в качестве двумерного, нам потребуется сделать специальный класс-обёртку, которая и будет реализовывать необходимое поведение.
Зададим для "обёртки" следующие ограничения:
Напомним, что проектируемый класс просто обёртка над уже существующим 1D массивом:
Создадим к показанному выше 1D-массиву класс-обёртку:
Здесь
-arr_ptr - ссылка на указатель (ссылка на массив). Именно этим приёмом (ссылкой) гарантируется, что класс-обёртка отреагирует на изменение исходного указателя (одномерного массива). Пример: память исходного массива была освобождена, а указатель установлен в NULL. Тогда, если по ошибке будет использована обёртка, произойдёт исключение по разыменованию NULL-указателя. Второй эффект такой реализации: если исходный указатель будет указывать на новый участок памяти (допустим, другой массив и т.п.), то и "обёртка" будет указывать туда же. При этом она будет считать, что размерность нового массива полностью соответствует старому!
-n - размерность массива [n,n]. Объявлена константой, т.к. изменение размерности у исходного массива произойти не может - для этого нужно выделить новую область памяти, скопировать туда данные, а затем освободить "старую" область.
arr_ptr и n размещены с секции private, чтобы избежать случайной порчи этих членов класса - вся работа с ними скрыта от пользователя. Более того, само это сокрытие, т.е. предоставление прозрачного интерфейса для обращения по индексам (и сокрытие механизма пересчёта индексом) и является целью создания класса-обёртки.
-arrWrapper(CT *const &ptr, const long N) - конструктор, принимающий в качестве параметров указатель (ссылку на указатель) на выделенную область памяти и соответствующий ей размер двумерного массива. Т.к. изменять значения этих параметров в процессе инициализации не требуется, они имеют квалификатор const.
- CT& operator()(const int i, const int j)const - определённая нами версия оператора (), которая и осуществляет пересчёт "двумерного" индекса в "одномерный". По реализации полностью совпадает с функцией пересчета из предыдущего поста. Обращение к элементу будет осуществляться как wrp(i,j), где wrp - "связанная" с некоторым одномерным массивом "обёртка".
- при "ручном пересчёте" есть вероятность "глупой ошибки" вроде опечатки и т.п.;
- использование функции "удлиняет" форму записи и делает менее прозрачным для понимания код;
- и "ручной пересчёт", и вызов функции выглядят непривычно по сравнению с традиционной адресацией двумерного массива "[][]".
В C++ существует возможность перегрузки операторов, которая позволяет задать собственное поведение операторов пользовательских типов. Т.к. переопределить операторы стандартных типов нельзя, то повлиять на поведение '[]' одномерного массива мы никак не можем. Поэтому, чтобу "подружить" одномерный массив с его интерпритацией в качестве двумерного, нам потребуется сделать специальный класс-обёртку, которая и будет реализовывать необходимое поведение.
Зададим для "обёртки" следующие ограничения:
- не занимается управлением памятью, в т.ч. не выделяет память под массив, не освобождает её;
- при инициализации (на этапе декларации) получает в качестве входных параметров-констант А) адрес начала памяти (одномерного массива), Б) размерность массива (предполагаем матрицу квадратной);
- должна отражать поведение, как если бы использовался исходный указатель;
- должна позволять производить чтение и модификацию элемента массива.
Напомним, что проектируемый класс просто обёртка над уже существующим 1D массивом:
typedef long CT; // Cell Type of the array
/* ... */
CT *arr = new(nothrow) CT[n*n]; //pointer to 1D-array of n*n elements
if(!arr){ // no memory allocated
cout << "The array was not allocated" << endl;
return 1;
}
Создадим к показанному выше 1D-массиву класс-обёртку:
class arrWrapper{
private:
CT *const &arr_ptr; //pointer to 1D array of n*n size
const long n; // dimension of the square matrix [n,n]
public:
arrWrapper(CT *const &ptr, const long N):arr_ptr(ptr), n(N) {};
inline CT& operator()(const int i, const int j)const
{ return arr_ptr[i*n+j]; };
};
Здесь
-arr_ptr - ссылка на указатель (ссылка на массив). Именно этим приёмом (ссылкой) гарантируется, что класс-обёртка отреагирует на изменение исходного указателя (одномерного массива). Пример: память исходного массива была освобождена, а указатель установлен в NULL. Тогда, если по ошибке будет использована обёртка, произойдёт исключение по разыменованию NULL-указателя. Второй эффект такой реализации: если исходный указатель будет указывать на новый участок памяти (допустим, другой массив и т.п.), то и "обёртка" будет указывать туда же. При этом она будет считать, что размерность нового массива полностью соответствует старому!
-n - размерность массива [n,n]. Объявлена константой, т.к. изменение размерности у исходного массива произойти не может - для этого нужно выделить новую область памяти, скопировать туда данные, а затем освободить "старую" область.
arr_ptr и n размещены с секции private, чтобы избежать случайной порчи этих членов класса - вся работа с ними скрыта от пользователя. Более того, само это сокрытие, т.е. предоставление прозрачного интерфейса для обращения по индексам (и сокрытие механизма пересчёта индексом) и является целью создания класса-обёртки.
-arrWrapper(CT *const &ptr, const long N) - конструктор, принимающий в качестве параметров указатель (ссылку на указатель) на выделенную область памяти и соответствующий ей размер двумерного массива. Т.к. изменять значения этих параметров в процессе инициализации не требуется, они имеют квалификатор const.
- CT& operator()(const int i, const int j)const - определённая нами версия оператора (), которая и осуществляет пересчёт "двумерного" индекса в "одномерный". По реализации полностью совпадает с функцией пересчета из предыдущего поста. Обращение к элементу будет осуществляться как wrp(i,j), где wrp - "связанная" с некоторым одномерным массивом "обёртка".
Полный пример создания и использования двумерного динамического массива (на основе 1D-массива и перегрузки оператора ())
#include<stdio.h> //to use memset
#include<iostream> //to use cin, cout
typedef long CT; // Cell Type of the array
class arrWrapper{//Wrapper for 1D array
private:
CT *const &arr_ptr; //pointer to 1D array of n*n size
const long n; // dimension of the square matrix [n,n]
arrWrapper& operator=(arrWrapper &)const{}; //to prevent
//default '=" operator, not needed to show the ()
public:
arrWrapper(CT *const &ptr, const long N):
arr_ptr(ptr), n(N) {};
inline CT& operator()(const int i, const int j)const
{ return arr_ptr[i*n+j]; };
};
int main (int argc, char** argv)
{
long n = 0; //variable to obtaint 2D-array size
using namespace std; // to be able to use cin & cout
cout << "Please, inter the matrix size n = ";
//GET ARRAY SIZE
cin >> n; // read size n from keyboard
//GET MEMORY FOR 1D ARRAY OF n*n SIZE
CT *arr = new(nothrow) CT[n*n]; //pointer to 1D-array
//of n*n elements
if(!arr){ // no memory allocated
cout << "The array was not allocated" << endl;
return 1;
}
//Fill array with char(0) values for sizeof(int)*n*n bytes
memset(arr, static_cast<char>(0),
(sizeof(long)/sizeof(char))*n*n);
//************************************************
//INIT ('link') WRAPPER WITH THE 1D ARRAY and SIZE
//************************************************
//DECLARE WRAPPER
arrWrapper wrp(arr, n);//derlare & init with array
//USE WRAPPER
for (int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
//ACCESS ELEMENT [i,j] USING the WRAPPER
wrp(i,j) = 10000*i+j;
}
}
//FREE MEM
delete [] arr;
arr = NULL;