矩阵定义,添加两个不同数据类型的矩阵
Matrix definition, adding two different data type matrices
我正在用 C 编程语言编写自己的 Matrix 类型版本。我也在尝试使用信息隐藏,所以我将我的代码分为两个不同的文件:一个包含类型定义和函数原型的头文件和一个包含代码的文件。
因为我需要使用方阵,所以我定义了我的矩阵结构如下:
struct matrix {
int dim;
enum type matrix_type;
union {
int ** int_matrix;
long ** long_matrix;
float ** float_matrix;
double ** double_matrix;
} data;
};
枚举类型定义如下:
enum type {M_INT = 1, M_LONG = 2, M_FLOAT = 3, M_DOUBLE = 4};
矩阵类型在头文件中定义如下:
typedef struct matrix * Matrix;
构建和销毁函数工作正常,填充函数也工作,我也找到了计算矩阵行列式的方法。
为了应对将 Matrix->data 拆分为不同类型的选择,我大量使用了 switch 语句,如下代码所示:
static int terminate(char * message)
{
fprintf(stderr, "%s\n", message);
exit(EXIT_FAILURE);
}
static int set_dim (Matrix m, int dim)
{
m->dim = dim;
return 0;
}
static int set_type (Matrix m, int t)
{
if (t < 1 || t > 4) {
terminate("Matrix type not valid.");
}
m->matrix_type = t;
return 0;
}
int create_matrix (Matrix * m, int m_dim, int m_type)
{
if ( ( (*m) = malloc( sizeof(struct matrix) ) ) == NULL )
terminate("Not possible to create a new matrix type.");
set_dim(*m, m_dim);
set_type(*m, m_type);
switch (m_type) {
case M_INT:
if ( !( (*m)->data.int_matrix = (int **) malloc(m_dim * sizeof(*((*m)->data.int_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.int_matrix[i] = (int *) malloc(m_dim * sizeof(*((*m)->data.int_matrix[i])))) )
terminate("Not enough space.");
init_int_matrix((*m)->dim, (*m)->data.int_matrix);
break;
case M_LONG:
if ( !( (*m)->data.long_matrix = (long **) malloc(m_dim * sizeof(*((*m)->data.long_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.long_matrix[i] = (long *) malloc(m_dim * sizeof(*((*m)->data.long_matrix[i])))) )
terminate("Not enough space.");
init_long_matrix((*m)->dim, (*m)->data.long_matrix);
break;
case M_FLOAT:
if ( !( (*m)->data.float_matrix = (float **) malloc(m_dim * sizeof(*((*m)->data.float_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.float_matrix[i] = (float *) malloc(m_dim * sizeof(*((*m)->data.float_matrix[i])))) )
terminate("Not enough space.");
init_float_matrix((*m)->dim, (*m)->data.float_matrix);
break;
case M_DOUBLE:
if ( !( (*m)->data.double_matrix = (double **) malloc(m_dim * sizeof(*((*m)->data.double_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.double_matrix[i] = (double *) malloc(m_dim * sizeof(*((*m)->data.double_matrix[i])))) )
terminate("Not enough space.");
init_double_matrix((*m)->dim, (*m)->data.double_matrix);
break;
}
return 0;
}
现在我想写一个函数来添加两个矩阵。但是,似乎我坚持使用自己的 Matrix 定义。简单的选择是编写一个函数,仅当它们共享相同的 matrix_type 时才将两个矩阵相加,否则返回 false 或 -1;但是,尽管有内部数据定义(int、long、float、double),编写一个函数来添加两个矩阵又如何呢?
有什么优雅实用的方法可以做到这一点,而不是编写一个带有嵌套 if 语句的函数来检查任何可能的类型耦合吗?
此外,我考虑和考虑我选择定义一个struct matrix。按 int、long、float 和 double 拆分矩阵数据有什么真正的优势吗?如果我只定义一个 double 数据类型,然后在需要时将其转换回 int 或 long 会怎样?在第二种情况下,我可能会摆脱指针和联合。
编辑:我目前使用的是 gcc 9.3.0 编译器,gnu11。
这个问题的处理方式不同。在您当前的实现中,您必须 显式处理所有类型的所有可能组合。添加新类型越来越难,因为它增加了复杂性并增加了组合的数量。
这是一个常见问题。我们都知道。因此,在您开始制作 a big pile of endless, unreadable and unmaintainable switches with many hidden bugs 之前,请阅读虚拟 table、object-oriented C 编程以及泛型编程和模板。
抽象接口是用虚函数和虚table完成的。比如我可以点file_operations from Linux kernel and GNOME glib library design.
在 C 中没有制作模板的好方法 - 如果您需要它们,请转到另一种编程语言,如 Rust 或 C++。在 C 中,您可以执行 big pile of endless macros (that are hard to document and hard to program and have unreadable error messages), or use define/undef tricks(这会好一点,但您永远不知道错误是关于哪个传递的),或者使用函数指针抽象每个操作,就像在 GNOME glib 库或 qsort
会(这会导致调用函数的运行时成本),或者使用 C 之外的东西来生成代码。
下面是“一大堆无穷无尽的宏”方法,它为自定义类型定义模板矩阵,并在所选命名空间内包含所有伴随函数。还有 3 种不同类型的 matrix_add 函数的定义。最后,一个简短的 main
显示了使用示例,2x2 int
+ 2x2 double
矩阵加法产生 float
矩阵。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
// ---------------------------------------------------------------
// Put this in a header
#define MATRIX_H(M, TYPE) \
\
/* the matrix type, for namespace access */ \
typedef TYPE M##matrix_type; \
\
/* the structure that represetntes a matrix */ \
typedef struct { \
size_t dim; \
TYPE *arr; \
} M##matrix; \
\
int M##matrix_create(M##matrix *m, size_t dim); \
int M##matrix_create_from_array(M##matrix *m, size_t N, TYPE (*arr)[N]); \
void M##matrix_destroy(M##matrix *m); \
TYPE *M##matrix_getp(M##matrix *m, size_t x, size_t y); \
TYPE M##matrix_get(const M##matrix *m, size_t x, size_t y); \
int M##matrix_add(M##matrix *r, const M##matrix *a, const M##matrix *b); \
void M##matrix_print(M##matrix *m); \
\
MATRIX_OP3_H(M, M, M) \
\
/* */
// put this in a source file
#define MATRIX_C(M, TYPE, PRINTF) \
\
int M##matrix_create(M##matrix *m, size_t dim) { \
m->arr = malloc(sizeof(TYPE) * dim * dim); \
if (!m->arr) return -ENOMEM; \
m->dim = dim; \
return 0; \
} \
\
int M##matrix_create_from_array(M##matrix *m, size_t N, TYPE (*arr)[N]) { \
int r = M##matrix_create(m, N); \
if (r) return r; \
const size_t dim = m->dim; \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
*M##matrix_getp(m, x, y) = arr[x][y]; \
} \
} \
return 0; \
} \
\
void M##matrix_destroy(M##matrix *m) { \
free(m->arr); \
} \
\
size_t M##matrix_dim(const M##matrix *m) { \
return m->dim; \
} \
\
TYPE *M##matrix_getp(M##matrix *m, size_t x, size_t y) { \
return &m->arr[x * m->dim + y]; \
} \
\
TYPE M##matrix_get(const M##matrix *m, size_t x, size_t y) { \
return *M##matrix_getp((M##matrix *)m, x, y); \
} \
\
void M##matrix_print(M##matrix *m) { \
const size_t dim = m->dim; \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
printf("%"PRINTF "%s", \
M##matrix_get(m, x, y), \
y + 1 == dim ? "\n" : " "); \
} \
} \
} \
\
/* stub implementation */ \
TYPE M##M##M##matrix_elem_add(TYPE a, TYPE b) { \
return a + b; \
} \
\
MATRIX_OP3_C(M, M, M) \
\
int M##matrix_add(M##matrix *m, const M##matrix *a, const M##matrix *b) { \
return M##M##M##matrix_add(m, a, b); \
} \
\
/* */
// shortcut for both
#define MATRIX_HC(M, TYPE, PRINTF) \
MATRIX_H(M, TYPE) \
MATRIX_C(M, TYPE, PRINTF)
// ---------------------------------------------------------------
#define MATRIX_OP3_H(M, A, B) \
\
/* callback for adding 2 variables of differnet types */ \
/* must be implemented by user */ \
M##matrix_type M##A##B##matrix_elem_add(A##matrix_type a, B##matrix_type b); \
\
int M##A##B##matrix_add(M##matrix *m, const A##matrix *a, const B##matrix *b); \
\
/* */
#define MATRIX_OP3_C(M, A, B) \
\
int M##A##B##matrix_add(M##matrix *m, const A##matrix *a, const B##matrix *b) { \
assert(M##matrix_dim(m) == A##matrix_dim(a)); \
assert(M##matrix_dim(m) == B##matrix_dim(b)); \
const size_t dim = M##matrix_dim(m); \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
*M##matrix_getp(m, x, y) = M##A##B##matrix_elem_add( \
A##matrix_get(a, x, y), B##matrix_get(b, x, y)); \
} \
} \
} \
\
/* */
#define MATRIX_OP3_HC(M, A, B) \
MATRIX_OP3_H(M, A, B) \
MATRIX_OP3_C(M, A, B)
// ---------------------------------------------------------------
// iimatrix_* operations are for integer matrix
MATRIX_HC(ii, int, "d")
// ddmatrix_* operations are for double matrix
MATRIX_HC(dd, double, "f")
// asa above
MATRIX_HC(ff, float, "f")
// callback for adding int + double = float
float ffiiddmatrix_elem_add(int a, double b) {
return a + b;
}
// define integer + double = float matrix operation
MATRIX_OP3_HC(ff, ii, dd)
int main() {
// create an integer matrix from array
iimatrix ii;
iimatrix_create_from_array(&ii, 2, (int[2][2]){{1,2},{3,4}});
printf("int matrix:\n");
iimatrix_print(&ii);
printf("\n");
// create double matrix from array
ddmatrix dd;
ddmatrix_create_from_array(&dd, 2, (double[2][2]){{5,6},{7,8}});
printf("double matrix:\n");
ddmatrix_print(&dd);
printf("\n");
// create float matrix and add integer to double matrixes
ffmatrix ff;
ffmatrix_create(&ff, 2);
ffiiddmatrix_add(&ff, &ii, &dd);
printf("resulting float matrix:\n");
ffmatrix_print(&ff);
printf("\n");
iimatrix_destroy(&ii);
ddmatrix_destroy(&dd);
ffmatrix_destroy(&ff);
}
代码输出:
int matrix:
1 2
3 4
double matrix:
5.000000 6.000000
7.000000 8.000000
resulting float matrix:
6.000000 8.000000
10.000000 12.000000
为了简化从二次到线性复杂性的所有类型组合的处理,您可以将一个或两个参数转换为通用的适当类型,并且只处理相同类型的操作。
您仍然需要在某些特殊情况下精确定义语义,例如
- 你如何处理整数溢出?
- 当矩阵乘以标量时,你是舍入值还是改变矩阵类型?
除非你有很好的理由处理 double
以外的值类型,否则我建议你先为这种情况编写代码。
我正在用 C 编程语言编写自己的 Matrix 类型版本。我也在尝试使用信息隐藏,所以我将我的代码分为两个不同的文件:一个包含类型定义和函数原型的头文件和一个包含代码的文件。
因为我需要使用方阵,所以我定义了我的矩阵结构如下:
struct matrix {
int dim;
enum type matrix_type;
union {
int ** int_matrix;
long ** long_matrix;
float ** float_matrix;
double ** double_matrix;
} data;
};
枚举类型定义如下:
enum type {M_INT = 1, M_LONG = 2, M_FLOAT = 3, M_DOUBLE = 4};
矩阵类型在头文件中定义如下:
typedef struct matrix * Matrix;
构建和销毁函数工作正常,填充函数也工作,我也找到了计算矩阵行列式的方法。 为了应对将 Matrix->data 拆分为不同类型的选择,我大量使用了 switch 语句,如下代码所示:
static int terminate(char * message)
{
fprintf(stderr, "%s\n", message);
exit(EXIT_FAILURE);
}
static int set_dim (Matrix m, int dim)
{
m->dim = dim;
return 0;
}
static int set_type (Matrix m, int t)
{
if (t < 1 || t > 4) {
terminate("Matrix type not valid.");
}
m->matrix_type = t;
return 0;
}
int create_matrix (Matrix * m, int m_dim, int m_type)
{
if ( ( (*m) = malloc( sizeof(struct matrix) ) ) == NULL )
terminate("Not possible to create a new matrix type.");
set_dim(*m, m_dim);
set_type(*m, m_type);
switch (m_type) {
case M_INT:
if ( !( (*m)->data.int_matrix = (int **) malloc(m_dim * sizeof(*((*m)->data.int_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.int_matrix[i] = (int *) malloc(m_dim * sizeof(*((*m)->data.int_matrix[i])))) )
terminate("Not enough space.");
init_int_matrix((*m)->dim, (*m)->data.int_matrix);
break;
case M_LONG:
if ( !( (*m)->data.long_matrix = (long **) malloc(m_dim * sizeof(*((*m)->data.long_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.long_matrix[i] = (long *) malloc(m_dim * sizeof(*((*m)->data.long_matrix[i])))) )
terminate("Not enough space.");
init_long_matrix((*m)->dim, (*m)->data.long_matrix);
break;
case M_FLOAT:
if ( !( (*m)->data.float_matrix = (float **) malloc(m_dim * sizeof(*((*m)->data.float_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.float_matrix[i] = (float *) malloc(m_dim * sizeof(*((*m)->data.float_matrix[i])))) )
terminate("Not enough space.");
init_float_matrix((*m)->dim, (*m)->data.float_matrix);
break;
case M_DOUBLE:
if ( !( (*m)->data.double_matrix = (double **) malloc(m_dim * sizeof(*((*m)->data.double_matrix)))) )
terminate("Not enough space.");
for(int i = 0; i < m_dim; i++)
if (!( (*m)->data.double_matrix[i] = (double *) malloc(m_dim * sizeof(*((*m)->data.double_matrix[i])))) )
terminate("Not enough space.");
init_double_matrix((*m)->dim, (*m)->data.double_matrix);
break;
}
return 0;
}
现在我想写一个函数来添加两个矩阵。但是,似乎我坚持使用自己的 Matrix 定义。简单的选择是编写一个函数,仅当它们共享相同的 matrix_type 时才将两个矩阵相加,否则返回 false 或 -1;但是,尽管有内部数据定义(int、long、float、double),编写一个函数来添加两个矩阵又如何呢? 有什么优雅实用的方法可以做到这一点,而不是编写一个带有嵌套 if 语句的函数来检查任何可能的类型耦合吗?
此外,我考虑和考虑我选择定义一个struct matrix。按 int、long、float 和 double 拆分矩阵数据有什么真正的优势吗?如果我只定义一个 double 数据类型,然后在需要时将其转换回 int 或 long 会怎样?在第二种情况下,我可能会摆脱指针和联合。
编辑:我目前使用的是 gcc 9.3.0 编译器,gnu11。
这个问题的处理方式不同。在您当前的实现中,您必须 显式处理所有类型的所有可能组合。添加新类型越来越难,因为它增加了复杂性并增加了组合的数量。
这是一个常见问题。我们都知道。因此,在您开始制作 a big pile of endless, unreadable and unmaintainable switches with many hidden bugs 之前,请阅读虚拟 table、object-oriented C 编程以及泛型编程和模板。
抽象接口是用虚函数和虚table完成的。比如我可以点file_operations from Linux kernel and GNOME glib library design.
在 C 中没有制作模板的好方法 - 如果您需要它们,请转到另一种编程语言,如 Rust 或 C++。在 C 中,您可以执行 big pile of endless macros (that are hard to document and hard to program and have unreadable error messages), or use define/undef tricks(这会好一点,但您永远不知道错误是关于哪个传递的),或者使用函数指针抽象每个操作,就像在 GNOME glib 库或 qsort
会(这会导致调用函数的运行时成本),或者使用 C 之外的东西来生成代码。
下面是“一大堆无穷无尽的宏”方法,它为自定义类型定义模板矩阵,并在所选命名空间内包含所有伴随函数。还有 3 种不同类型的 matrix_add 函数的定义。最后,一个简短的 main
显示了使用示例,2x2 int
+ 2x2 double
矩阵加法产生 float
矩阵。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
// ---------------------------------------------------------------
// Put this in a header
#define MATRIX_H(M, TYPE) \
\
/* the matrix type, for namespace access */ \
typedef TYPE M##matrix_type; \
\
/* the structure that represetntes a matrix */ \
typedef struct { \
size_t dim; \
TYPE *arr; \
} M##matrix; \
\
int M##matrix_create(M##matrix *m, size_t dim); \
int M##matrix_create_from_array(M##matrix *m, size_t N, TYPE (*arr)[N]); \
void M##matrix_destroy(M##matrix *m); \
TYPE *M##matrix_getp(M##matrix *m, size_t x, size_t y); \
TYPE M##matrix_get(const M##matrix *m, size_t x, size_t y); \
int M##matrix_add(M##matrix *r, const M##matrix *a, const M##matrix *b); \
void M##matrix_print(M##matrix *m); \
\
MATRIX_OP3_H(M, M, M) \
\
/* */
// put this in a source file
#define MATRIX_C(M, TYPE, PRINTF) \
\
int M##matrix_create(M##matrix *m, size_t dim) { \
m->arr = malloc(sizeof(TYPE) * dim * dim); \
if (!m->arr) return -ENOMEM; \
m->dim = dim; \
return 0; \
} \
\
int M##matrix_create_from_array(M##matrix *m, size_t N, TYPE (*arr)[N]) { \
int r = M##matrix_create(m, N); \
if (r) return r; \
const size_t dim = m->dim; \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
*M##matrix_getp(m, x, y) = arr[x][y]; \
} \
} \
return 0; \
} \
\
void M##matrix_destroy(M##matrix *m) { \
free(m->arr); \
} \
\
size_t M##matrix_dim(const M##matrix *m) { \
return m->dim; \
} \
\
TYPE *M##matrix_getp(M##matrix *m, size_t x, size_t y) { \
return &m->arr[x * m->dim + y]; \
} \
\
TYPE M##matrix_get(const M##matrix *m, size_t x, size_t y) { \
return *M##matrix_getp((M##matrix *)m, x, y); \
} \
\
void M##matrix_print(M##matrix *m) { \
const size_t dim = m->dim; \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
printf("%"PRINTF "%s", \
M##matrix_get(m, x, y), \
y + 1 == dim ? "\n" : " "); \
} \
} \
} \
\
/* stub implementation */ \
TYPE M##M##M##matrix_elem_add(TYPE a, TYPE b) { \
return a + b; \
} \
\
MATRIX_OP3_C(M, M, M) \
\
int M##matrix_add(M##matrix *m, const M##matrix *a, const M##matrix *b) { \
return M##M##M##matrix_add(m, a, b); \
} \
\
/* */
// shortcut for both
#define MATRIX_HC(M, TYPE, PRINTF) \
MATRIX_H(M, TYPE) \
MATRIX_C(M, TYPE, PRINTF)
// ---------------------------------------------------------------
#define MATRIX_OP3_H(M, A, B) \
\
/* callback for adding 2 variables of differnet types */ \
/* must be implemented by user */ \
M##matrix_type M##A##B##matrix_elem_add(A##matrix_type a, B##matrix_type b); \
\
int M##A##B##matrix_add(M##matrix *m, const A##matrix *a, const B##matrix *b); \
\
/* */
#define MATRIX_OP3_C(M, A, B) \
\
int M##A##B##matrix_add(M##matrix *m, const A##matrix *a, const B##matrix *b) { \
assert(M##matrix_dim(m) == A##matrix_dim(a)); \
assert(M##matrix_dim(m) == B##matrix_dim(b)); \
const size_t dim = M##matrix_dim(m); \
for (size_t x = 0; x < dim; ++x) { \
for (size_t y = 0; y < dim; ++y) { \
*M##matrix_getp(m, x, y) = M##A##B##matrix_elem_add( \
A##matrix_get(a, x, y), B##matrix_get(b, x, y)); \
} \
} \
} \
\
/* */
#define MATRIX_OP3_HC(M, A, B) \
MATRIX_OP3_H(M, A, B) \
MATRIX_OP3_C(M, A, B)
// ---------------------------------------------------------------
// iimatrix_* operations are for integer matrix
MATRIX_HC(ii, int, "d")
// ddmatrix_* operations are for double matrix
MATRIX_HC(dd, double, "f")
// asa above
MATRIX_HC(ff, float, "f")
// callback for adding int + double = float
float ffiiddmatrix_elem_add(int a, double b) {
return a + b;
}
// define integer + double = float matrix operation
MATRIX_OP3_HC(ff, ii, dd)
int main() {
// create an integer matrix from array
iimatrix ii;
iimatrix_create_from_array(&ii, 2, (int[2][2]){{1,2},{3,4}});
printf("int matrix:\n");
iimatrix_print(&ii);
printf("\n");
// create double matrix from array
ddmatrix dd;
ddmatrix_create_from_array(&dd, 2, (double[2][2]){{5,6},{7,8}});
printf("double matrix:\n");
ddmatrix_print(&dd);
printf("\n");
// create float matrix and add integer to double matrixes
ffmatrix ff;
ffmatrix_create(&ff, 2);
ffiiddmatrix_add(&ff, &ii, &dd);
printf("resulting float matrix:\n");
ffmatrix_print(&ff);
printf("\n");
iimatrix_destroy(&ii);
ddmatrix_destroy(&dd);
ffmatrix_destroy(&ff);
}
代码输出:
int matrix:
1 2
3 4
double matrix:
5.000000 6.000000
7.000000 8.000000
resulting float matrix:
6.000000 8.000000
10.000000 12.000000
为了简化从二次到线性复杂性的所有类型组合的处理,您可以将一个或两个参数转换为通用的适当类型,并且只处理相同类型的操作。
您仍然需要在某些特殊情况下精确定义语义,例如
- 你如何处理整数溢出?
- 当矩阵乘以标量时,你是舍入值还是改变矩阵类型?
除非你有很好的理由处理 double
以外的值类型,否则我建议你先为这种情况编写代码。