Форум программистов, компьютерный форум, киберфорум
L0M
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Простая реализация матрицы на C

Запись от L0M размещена 16.02.2020 в 04:35

Бывалые Си-шники наверное не найдут в нижеизложенном ничего нового. Однако для начинающих может быть любопытно.

В момент особо острого приступа прокрастинации мне в голову пришла мысль попробовать реализовать на чистом С двухмерный массив (матрицу) в стиле ООП. Чистый С я знаю не так хорошо, как хотелось бы, поэтому было интересно.

Поставленные цели:
  1. Создать тип Matrix2 (двухмерная матрица) и набор функций, которые с ним работают. Далее тип и набор функций буду для краткости называть просто Matrix2.
  2. Matrix2 должен реализовывать базовую функциональность двухмерного массива: создание, удаление, доступ к элементу на чтение и запись.
  3. Matrix2 должен скрывать своё внутреннее устройство (инкапсуляция).
  4. Исходя из предыдущего пункта, Matrix2 должен предоставлят достаточный интерфейс для работы с данными.
  5. Matrix2 должен быть по возможности безопасен в плане неправильного использования.
  6. Matrix2 должен быть независимым от типа данных (шаблоны С++?).
  7. ...и навеное что-то ещё...

После некоторых раздумий родилось следующее.

Рождалось, кстати, в 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. Код с пристрастием не тестировал, поскольку см. предыдущий абзац.
Размещено в Без категории
Просмотров 265 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.