行列乗算用の C 関数を作成しています。int の 2D 配列を 2 つ取ります。入力配列の次元がわかっている場合はこれを行うことができますが、より一般的な関数を作成したいと考えています。
それらの寸法を見つける方法と、コンパイル時に製品の寸法がわからない場合に配列を返す方法は?
行列乗算用の C 関数を作成しています。int の 2D 配列を 2 つ取ります。入力配列の次元がわかっている場合はこれを行うことができますが、より一般的な関数を作成したいと考えています。
それらの寸法を見つける方法と、コンパイル時に製品の寸法がわからない場合に配列を返す方法は?
配列の先頭へのポインターしかない場合、配列の次元を見つけることはできません。配列の次元を関数に渡す必要があります。
この答えは完全にやり過ぎですが、これはstruct
nm が「まったく別の話」と呼んだ私のベースのアプローチです。
これが新しいプログラムであり (つまり、関数への入力/出力がどのように見えるかを定義する必要がある場合)、行列を使って多くの作業を行うつもりなら、行列を表すために構造体を使用する傾向があります。その構造体のインスタンスへのポインターを渡します。
以下のコードは、考えられるアプローチの 1 つを示しています。ここでいくつかの「トリック」が行われていることに注意してください。まず第一に、データはすべて 1 つの連続したブロックに格納されます。これにより、さまざまな理由でパフォーマンスが向上する可能性があります。この手法の潜在的な欠点の 1 つは、まったく新しいインスタンスを割り当ててデータをコピーする必要があるため、マトリックスのサイズ変更にコストがかかることです。しかし、それが問題であることがわかった場合は、マトリックス内の値にアクセスするために常に matrix_get() および matrix_set() 関数を使用すると仮定して、いつでも実装を変更できます。
また、行列構造体とデータ ポインターが指すメモリはすべて、1 回の malloc 呼び出しで割り当てられます。この手法を使用する場合は、データ アライメントの問題に注意してください。たとえば、データを変更して 64 ビット整数または double を指すようにする場合は、すべてが 8 バイト アラインされるようにパディングを追加する必要があります。または、関数malloc
内の別の配列としてデータ ポインターのみnew_matrix()
を解放することを忘れないでくださいfree_matrix()
。
乗算関数を記述するための OP への演習として残しました。
#include <stdio.h>
#include <stdlib.h>
struct matrix
{
int rows;
int cols;
int * data;
};
struct matrix * new_matrix( int rows, int cols )
{
struct matrix * m = NULL;
/* Allocate a block of memory large enough to hold the matrix 'header' struct
* as well as all the row/column data */
m = malloc( sizeof(struct matrix) + (rows * cols * sizeof(int) ) );
if( m )
{
m->rows = rows;
m->cols = cols;
/* Some ugly pointer math to get to the first byte of data */
m->data = (int*) ( (char *) m + sizeof(*m) );
}
return m;
}
void free_matrix( struct matrix * m )
{
free( m );
}
int matrix_set( struct matrix * m, int row, int col, int val)
{
if( col >= m->cols || row >= m->rows )
return -1;
m->data[ m->cols * row + col ] = val;
return 0;
}
int matrix_get( struct matrix * m, int row, int col, int * val)
{
if( col >= m->cols || row >= m->rows )
return -1;
else
{
*val = m->data[ m->cols * row + col ];
return 0;
}
}
void print_matrix( struct matrix * m )
{
int r,c;
int val;
for( r = 0; r < m->rows; r++ )
{
for( c = 0; c < m->cols; c++ )
{
matrix_get( m, r, c, &val );
printf( "%5d%s", val, c + 1 < m->cols ? "," : "" );
}
printf("\n");
}
}
int main (int argc, char **argv)
{
int r,c;
struct matrix * m = new_matrix( 5, 5 );
for( r = 0; r < m->rows; r++ )
{
for( c = 0; c < m->cols; c++ )
{
matrix_set( m, r, c, (r +1)* 10 + c + 1 );
}
}
print_matrix( m );
free_matrix( m );
return 0;
}
C には実際の 2D 配列はありません。配列の配列だけがあり、まったく同じではありません。(これを言うと殴られることはわかっています。我慢してください。)
違いはそれほど重要ではないように見えるかもしれませんが、そうです。配列を操作するために、コンパイル時にその次元を知る必要はありません。例えば
double dot_product (double a[], double b[], int size);
/* an A-OK function */
ただし、配列要素のサイズを知っている必要があります。例えば
void matrix_product (double a[][], double b[][], double result[][],
int a_rows, int a_cols, int b_cols);
/* Bad, would not compile. You are only permitted to say "double[N][]",
where N is known at compile time */
完全に一般的な行列操作コードが必要な場合は、単純な 1D 配列を使用し、行番号と列番号からインデックスを自分で計算する必要があります。また、寸法を渡す必要があります。
void matrix_product (double a[], double b[], double result[],
int a_rows, int a_cols, int b_cols) {
...
for (col = 0; col < a_cols; ++col) {
for (row = 0; row < a_rows; ++row) {
...
... a[row*a_cols + col] ...
...
この例では、呼び出し元result
は乗算関数ではなく を割り当てます。
より高いレベルでは、次のように、行列をその次元とともに抽象行列データ型の形式でカプセル化する必要があります。
struct matrix {
double* elements;
int rows, cols;
};
しかし、それはまったく別の話です。
配列の MATRIX 構造を作成することをお勧めします。@Davidが指摘したように、Cでは、自分でディメンションを追跡する必要があります。安全にこれを行う言語に組み込まれた関数はありません。
文字列 (つまり、\0 で終わる char 配列) には strlen() のような関数がありますが、配列などにはありません。これを追跡する最も簡単な方法は、独自の抽象データ型とヘルパー関数を作成することです。
これを試して:
typedef struct matrix {
int **array;
int rows;
int cols;
} matrix_t;
int createMatrix(matrix_t* mtx) {
int i;
if (!mtx) {
printf("INVALID POINTER TO MATRIX");
return -1;
}
if ( (mtx->rows == 0) || (mtx->cols == 0) ) {
printf("Rows/columns cannot be zero!");
return -1;
}
int status = 0;
// allocate the array
mtx->array = (int **)calloc(mtx->rows,sizeof(int*));
if(mtx->array != NULL) {
// memory allocation succeeded
for(i = 0; i < mtx->rows; i++) {
if((mtx->array[i] = (int*)calloc(mtx->cols,sizeof(int))) == NULL) {
printf("Memory allocation error");
status = -1;
break;
} else {
// Allocation successful
}
}
} else {
printf("Memory allocation error!");
status = -1;
}
return status;
}
int destroyMatrix(matrix_t* mtx) {
// destroy the array
for(i = 0; i < mtx->rows; i++) {
if(mtx->array[i] != NULL) {
free(mtx->array[i]);
mtx->array[i] = NULL;
}
}
if(mtx->array) {
free(mtx->array);
mtx->array = NULL;
}
return 0;
}
これで、新しいマトリックス構造を作成し、その行/列の値を設定し、createMatrix を呼び出すだけで、次のように設定できます。
matrix_t myMtx;
myMtx.array = NULL;
myMtx.rows = 3;
myMtx.cols = myMtx.cols; // Make it a square matrix
if(createMatrix(&myMtx) == 0 ) {
printf("Created matrix successfully");
} else {
printf("Failed to create matrix!");
}
これらの関数は、メモリ割り当てが失敗したかどうかも親切にチェックし、使用する前にすべてのポインターをチェックすることで、プログラムのクラッシュ (つまり、SEGFAULT) を回避します。
幸運を!
配列の次元を渡す必要があります。次に、結果を保持するために 3 番目の配列を動的に割り当てる必要があります。結果行列を計算したら、それへのポインタを返すことができます。
A が nxm 行列で、B が mxp 行列である場合に、次の行に沿って何かを試してください。
int* matix_multiply(int* matrix_a, int* matrix_b, int n, m, int p){
int *result = (int *) malloc(sizeof(int) * a_rows * b_cols);
if(result == NULL)
exit(EXIT_FAILURE);
//loops to do the actual multiplication and store the results in result[i][j]
return result; //returns the result array
}