Бывалые Си-шники наверное не найдут в нижеизложенном ничего нового. Однако для начинающих может быть любопытно.
В момент особо острого приступа прокрастинации мне в голову пришла мысль попробовать реализовать на чистом С двухмерный массив (матрицу) в стиле ООП. Чистый С я знаю не так хорошо, как хотелось бы, поэтому было интересно.
Поставленные цели:- Создать тип Matrix2 (двухмерная матрица) и набор функций, которые с ним работают. Далее тип и набор функций буду для краткости называть просто Matrix2.
- Matrix2 должен реализовывать базовую функциональность двухмерного массива: создание, удаление, доступ к элементу на чтение и запись.
- Matrix2 должен скрывать своё внутреннее устройство (инкапсуляция).
- Исходя из предыдущего пункта, Matrix2 должен предоставлят достаточный интерфейс для работы с данными.
- Matrix2 должен быть по возможности безопасен в плане неправильного использования.
- Matrix2 должен быть независимым от типа данных (шаблоны С++?).
- ...и навеное что-то ещё...
После некоторых раздумий родилось следующее.
Рождалось, кстати, в Visual Studio Community 2019, поэтому с другими компиляторами и другими ОС могут быть нюансы.
Для начала, synopsis, как говорят наши западные коллеги. Т.е. краткий пример того, как это можно использовать. Здесь, задавая для символа CASE значение 1 или 2 можно задать тип данных для матрицы: 1 - int , 2 - double .
C | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| #include <stdio.h>
#include <stdlib.h>
#include "matrix2.h"
#define M 3
#define N 5
#define CASE 1
#if CASE == 1
#define EL_TYPE int
#elif CASE == 2
#define EL_TYPE double
#else
#error Unsupported CASE
#endif
void printMatrix2double(const Matrix2 *m, const char *comment) {
if (comment)
printf("%s\n", comment);
if (!m)
printf("NULL\n");
else {
for (size_t i = 0; i < getRowsMatrix2(m); ++i) {
for (size_t j = 0; j < getColsMatrix2(m); ++j) {
#if CASE == 1
printf("%5d", *elMatrix2(EL_TYPE, m, i, j));
#elif CASE == 2
printf("%5.1lf", *elMatrix2(EL_TYPE, m, i, j));
#else
#error Unsupported CASE
#endif
}
printf("\n");
}
printf("\n");
}
}
int main(void)
{
Matrix2 *m1 = (Matrix2 *)malloc(sizeofMatrix2());
if (!m1) {
printf("malloc() fail\n");
return 1;
}
if (!allocMatrix2(EL_TYPE, m1, M, N)) {
printf("allocMatrix2() fail\n");
return 2;
}
for (size_t i = 0; i < getRowsMatrix2(m1); ++i)
for (size_t j = 0; j < getColsMatrix2(m1); ++j)
*elMatrix2(EL_TYPE, m1, i, j) = (EL_TYPE)((i + 1) * 10 + (j + 1));
printMatrix2double(m1, "m1:");
Matrix2 *m2 = (Matrix2 *)malloc(sizeofMatrix2());
if (!m2) {
printf("malloc() fail\n");
return 3;
}
if (!allocMatrix2(EL_TYPE, m2, N, M)) {
printf("allocMatrix2() fail\n");
return 4;
}
for (size_t i = 0; i < getRowsMatrix2(m2); ++i)
for (size_t j = 0; j < getColsMatrix2(m2); ++j)
*elMatrix2(EL_TYPE, m2, i, j) = (EL_TYPE)((i + 1) * 10 + (j + 1));
printMatrix2double(m2, "m2:");
printf("swap\n");
swapMatrix2(m1, m2);
printMatrix2double(m1, "m1:");
printMatrix2double(m2, "m2:");
printf("assignment\n");
if (!assignMatrix2(m2, m1)) {
printf("assignMatrix2() fail\n");
return 5;
}
printMatrix2double(m1, "m1:");
printMatrix2double(m2, "m2:");
freeMatrix2(m1);
free(m1);
freeMatrix2(m2);
free(m2);
return 0;
} |
|
Заголовочный файл. Этот файл общедоступен. В нём имеются прототипы функций, которые работают со структурой Martix2. Но самой структуры здесь нет, только её объявление. Определение структуры располагается в файле реализации. Если Matrix2 откомпилировать и распространять как библиотеку, то файл реализации может быть вообще недоступен программисту, который будет её использовать. Это поможет избежать вмешательства шаловливых ручек в реализацию Matrix2, а также использование полей структуры Martix2 нештатными способами.
Итого, Matrix2 представляет из себя чёрный ящик, взаимодействие с которым осуществляется посредством набора функций. Т.е некоего интерфейса.
Объект (экземпляр структуры) Matrix2 поддерживает свою внутреннюю непротиворечивость. Т.е. пользуясь интерфейсными функциями невозможно нарушить целостность объекта. С другой стороны, если имеется какой-то объект типа Matrix2, то с помощью интерфейсных функций можно получить всю информацию об объекте.
"Параметризация по типу" осуществляется двумя макросами. Выглядит несколько неуклюже, но работает. По типам данных в данной реализации имеется ограничение: тип данных должен быть POD, т.е. значение этого типа может быть корректно скопировано с помощью функции memcpy() .
В качестве бонуса есть функция, которая производит обмен содержимого двух матриц.
matrix2.h
C | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| #ifndef __MATRIX2_H__
#define __MATRIX2_H__
#include <crtdefs.h>
struct _Matrix2;
typedef struct _Matrix2 Matrix2;
// Возвращает размер структуры Matrix2 в байтах.
size_t sizeofMatrix2();
// Конструктор
// Возвращает 0, если произошла ошибка при выделении памяти для матрицы
// или если mx == NULL.
int _allocMatrix2(Matrix2 *mx, size_t rows, size_t cols, size_t typeSize);
#define allocMatrix2(TYPE, m, r, c) _allocMatrix2(m, r, c, sizeof(TYPE))
// Деструктор
// Освобождает память, помечает матрицу как некорректную.
void freeMatrix2(Matrix2 *mx);
// Проверка на корректность.
// Работает только после инициализации матрицы функцией allocMatrix2().
// Возваращает 1, если матрица корректна.
int isValidMatrix2(const Matrix2 *mx);
// Возвращает количество строк матрицы.
// Возвращает 0, если передан NULL или матрица некорректна.
size_t getRowsMatrix2(const Matrix2 *mx);
// Возвращает количество столбцов матрицы.
// Возвращает 0, если передан NULL или матрица некорректна.
size_t getColsMatrix2(const Matrix2 *mx);
// Доступ к элементу матрицы.
// Возвращает NULL, если передан NULL, матрица некорректна или индексы выходят за границу матрицы.
void *_elMatrix2(const Matrix2 *mx, size_t row, size_t col);
#define elMatrix2(TYPE, m, r, c) ((TYPE *)_elMatrix2(m, r, c))
// Присваивание/копирование
// Возвращает 0, если хотя бы один из аргументов равен NULL.
int assignMatrix2(Matrix2 *dst, const Matrix2 *src);
// Обмен значениями
// Возвращает 0, если хотя бы один из аргументов равен NULL.
int swapMatrix2(Matrix2 *mx1, Matrix2 *mx2);
#endif // !__MATRIX2_H__ |
|
Ну и собственно реализация.
Для хранения матрицы выделяется один кусок памяти нужного размера. Адрес элемента вычисляется в функции _elMatrix2() .
Хотел для солидности ещё что-то написать по реализации, но код слишком прост и очевиден.
martix2.c
C | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
| #include "matrix2.h"
#include <stdlib.h>
#include <memory.h>
struct _Matrix2 {
size_t m_rows; // количество строк матрицы
size_t m_cols; // количество столбцов матрицы
size_t m_typeSize; // размер типа для элемента матрицы в байтах
void *m_data; // указатель на данные
};
size_t sizeofMatrix2()
{
return sizeof(Matrix2);
}
int _allocMatrix2(Matrix2 *mx, size_t rows, size_t cols, size_t typeSize)
{
if (!mx)
return 0;
mx->m_rows = rows;
mx->m_cols = cols;
mx->m_typeSize = typeSize;
mx->m_data = malloc(rows * cols * typeSize);
return mx->m_data != NULL;
}
void freeMatrix2(Matrix2 *mx)
{
if (!mx)
return;
if (mx->m_data)
free(mx->m_data);
mx->m_data = NULL;
}
int isValidMatrix2(const Matrix2 *mx)
{
if (!mx || !mx->m_data)
return 0;
return 1;
}
size_t getRowsMatrix2(const Matrix2 *mx)
{
if (!mx || !mx->m_data)
return 0;
return mx->m_rows;
}
size_t getColsMatrix2(const Matrix2 *mx)
{
if (!mx || !mx->m_data)
return 0;
return mx->m_cols;
}
void *_elMatrix2(const Matrix2 *mx, size_t row, size_t col)
{
if (mx && row < mx->m_rows && col < mx->m_cols) {
return (void *)((char *)mx->m_data + (row * mx->m_cols + col) * mx->m_typeSize);
}
return NULL;
}
int assignMatrix2(Matrix2 *dst, const Matrix2 *src)
{
if (!src)
return 0;
void *mem = malloc(src->m_rows * src->m_cols * src->m_typeSize);
if (!mem)
return 0;
memcpy(mem, src->m_data, src->m_rows * src->m_cols * src->m_typeSize);
if (dst && isValidMatrix2(dst))
free(dst->m_data);
dst->m_rows = src->m_rows;
dst->m_cols = src->m_cols;
dst->m_data = mem;
dst->m_typeSize = src->m_typeSize;
return 1;
}
int swapMatrix2(Matrix2 *mx1, Matrix2 *mx2)
{
if (!mx1 || !mx2)
return 0;
size_t tmp_rows, tmp_cols, tmp_size;
void *tmp_mem;
tmp_rows = mx1->m_rows;
tmp_cols = mx1->m_cols;
tmp_mem = mx1->m_data;
tmp_size = mx1->m_typeSize;
mx1->m_rows = mx2->m_rows;
mx1->m_cols = mx2->m_cols;
mx1->m_data = mx2->m_data;
mx1->m_typeSize = mx2->m_typeSize;
mx2->m_rows = tmp_rows;
mx2->m_cols = tmp_cols;
mx2->m_data = tmp_mem;
mx2->m_typeSize = tmp_size;
return 1;
} |
|
PS. Я не ставил перед собой цель написать полноценную библиотеку, которая будет работать с любыми типами данных, на любом железе, под любой операционкой, в присутствии неприрученного студента-программиста с гранатой. Если вам понравилось - берите и развивайте.
PPS. Код с пристрастием не тестировал, поскольку см. предыдущий абзац. |