このページでは、C言語での「行列の扱い方」について解説していきます。
C言語で行列を扱う方法としては様々なものが考えられますが、このページでは一番基本的な「2次元配列を行列として扱う方法」について解説していきます。
また、このページでは行列演算は行わず、行列の成分の2次元配列への格納方法や、行列の表示方法等の基礎的な内容の解説のみを行います
基礎的な内容ですが、これらをしっかり理解していないと行列演算もうまくプログラミングできません。逆に、これらの内容をしっかり理解しておけば、行列演算等のプログラミングが楽になります。
行列演算はさまざまな場面で活躍しますので、是非このページの内容をしっかり理解していっていただければと思います!
2次元配列を変数宣言して行列を作成する
まず2次元配列を行列として扱う際には、2次元配列の変数宣言が必要になります。
2次元配列のサイズに行数と列数を指定する
2次元配列を変数宣言する際には2つのサイズを指定することができます。
2次元配列を行列として扱う際には、1つ目のサイズに「行列の行数」を、2つ目のサイズに「行列の列数」を指定して変数宣言を行うのが一番簡単だと思います。
型 変数名[行列の行数][行列の列数];
スポンサーリンク
2行3列の行列を作成する
例えば、下記のように2次元配列を変数宣言した場合、
int main(void) {
int matA[2][3]; /* 2行3列の行列 */
return 0;
}
上記の変数宣言により、プログラム内で下の図のような2次元配列を扱うことができるようになります。
実際には、メモリ上には全て1次元的に配置されるのですが、行列を扱う際の2次元配列のイメージとしては上の図で考えた方が分かりやすいと思います。
この2次元配列は、縦方向に2つずつ値が格納可能かつ横方向に3つずつデータが格納可能な配列ですので、「2行3列の行列」として扱うことができます。
上記の変数宣言時には型を int
にしているため、int
型の値を扱うための行列を作成することになります
行列で扱う値に応じて、型は適切に変更してください
M
行 N
列の行列を作成する
先程の例は2行3列の行列を扱う場合のものになりますが、M
行 N
列の行列を扱いたいのであれば、下記のように2次元配列を変数宣言すれば良いです。
#define M 20 /* 行数 */
#define N 10 /* 列数 */
int main(void) {
int matA[M][N]; /* M行N列の行列 */
return 0;
}
この変数宣言により、縦方向に M
個の値、横方向に N
個の値を格納可能な2次元配列をプログラム内で使用することができるようになり、この配列を M
行 N
列の行列として扱うことができます。
M
と N
は下記で定義していますので、これを変更することで様々な行数・列数の行列を扱うことができます。
#define M 20 /* 行数 */
#define N 10 /* 列数 */
2次元配列に行列の成分を格納する
ただし、変数宣言で行われるのは行列として扱える2次元配列の用意だけです。
実際に行列の演算等を行う際には、事前に行列の各成分を2次元配列に格納してやる必要があります。
スポンサーリンク
行列の成分の添字と配列の添字の関係
例えば下記のような2行3列の行列 \(A\) について考えてみましょう。
$$ A = \left ( \begin{array}{ccc} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \end{array} \right ) $$
上記では、行列の各成分を \(a_{i j}\) という形式で表しています。ここで、\(i\) は \(i\) 行目の成分であることを表す添字で、\(j\) は \(j\) 列目の成分であることを表す添字になります。つまり、この \(i\) と \(j\) は、行列の中の位置を示す添字となります。
例えば \(a_{2 1}\) は、行列 \(A\) における \(2\) 行目の \(1\) 列目の成分ということになります。
このような行列をC言語で扱う場合、行列の各成分を、その成分の位置に対応した2次元配列の要素に格納していくことになります。
より具体的には、行列として扱う2次元配列を matA
とすれば、matA[i][j]
に \(a_{i+1 j+1}\) を代入していくことで、行列の各成分を2次元配列に格納していきます。
配列の添字と成分の添字が異なるのは、C言語で扱う配列の添字が 0
から始まるのに対し、数学等で扱う行列の成分の添字は \(1\) から始まるのが一般的だからです。
上記のように2次元配列 matA
に行列の成分を格納した場合、matA[i][j]
と記述することで、行列の \(a_{i+1 j+1}\) 成分にアクセスすることができるようになります。
したがって、i
を変化させればアクセスできる成分の「行」が変化し、j
を変化させればアクセスできる成分の「列」が変化することになります。
1つずつ行列の成分を配列に格納する
例えば、\(a_{1 1}\) から \(a_{2 3}\) を下記のように設定した場合、
- \(a_{1 1} = 9\)
- \(a_{1 2} = 8\)
- \(a_{1 3} = 7\)
- \(a_{2 1} = 6\)
- \(a_{2 2} = 5\)
- \(a_{2 3} = 4\)
行列 \(A\) は下記のような行列となります。
$$ A = \left ( \begin{array}{ccc} 9 & 8 & 7 \\ 6 & 5 & 4 \end{array} \right ) $$
この行列を扱う2次元配列を matA
とすれば、matA[i][j]
に \(a_{i+1 j+1}\) を代入していけば良いわけですので、下記のようにソースコードを記述すれば、matA
を上記の行列 \(A\) として扱うことができることになります。
int main(void) {
int matA[2][3]; /* 2行3列の行列 */
matA[0][0] = 9; /* a11 */
matA[0][1] = 8; /* a12 */
matA[0][2] = 7; /* a13 */
matA[1][0] = 6; /* a21 */
matA[1][1] = 5; /* a22 */
matA[1][2] = 4; /* a23 */
return 0;
}
上記を実行すれば、2次元配列 matA
には下記のような値が格納されることになります。
ループで行列の成分を配列に格納する
ここまで2行3列の行列の例を用いてきましたが、「行列の各成分を、その成分の位置に対応した2次元配列の要素に格納していく」ことさえ意識すれば、他の行数や列数の行列への値の格納も同様にして行うことができます。
また、当然ですが、ループ処理で行列の成分を配列に格納することもできます。
例えば下記の4行5列の行列 \(A\) について考えてみましょう!
$$ A = \left ( \begin{array}{ccccc} 0 & 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 & 9 \\ 10 & 11 & 12 & 13 & 14 \\ 15 & 16 & 17 & 18 & 19 \end{array} \right ) $$
この場合、\(a_{i j}\) は \(5 * (i – 1) + (j – 1)\) の値となっていますので、行列を扱う2次元配列を matA
とすれば、matA[i][j]
には \(a_{i+1 j+1}\)、すなわち \(5 * i + j\) を格納していけば良いことになります。
したがって、下記のような処理により、行列 \(A\) の各成分を2次元配列 matA
に格納することができることになります。
int main(void) {
int matA[4][5]; /* 4行5列の行列 */
int i, j;
for (i = 0; i < 4; i++) {
for (j = 0; j < 5; j++) {
/* a i+1 i+j をmatA[i][j]に格納 */
matA[i][j] = 5 * i + j;
}
}
return 0;
}
スポンサーリンク
初期化時に行列の成分を配列に格納する
また、ここまで行列の各成分を1つ1つ代入したり、ループの中で代入したりしてきましたが、2次元配列の宣言時に初期化を行うことで、一度に行列の成分を2次元配列に格納することもできます。
int main(void) {
/* 4行5列の行列 */
int matA[4][5] = {
{0, 1, 2, 3, 4},
{5, 6, 7, 8, 9},
{10, 11, 12, 13, 14},
{15, 16, 17, 18, 19}
};
return 0;
}
scanf
で入力された行列の成分を配列に格納する
さらに、scanf
を利用して入力を受け付け、入力された値を行列の成分とするようなことも可能です。
例えば、下記のようにループの中で scanf
関数を実行するようにすれば、M
行 N
列の行列の各成分をユーザーによって入力することができるようになります。
#include <stdio.h>
#define M 4
#define N 5
int main(void) {
/* 4行5列の行列 */
int matA[4][5];
int i, j;
for (i = 0; i < M; i++) {
for (j = 0; j < N; j++) {
printf("a%d%d = ", i + 1, j + 1);
scanf("%d", &matA[i][j]);
}
}
return 0;
}
2次元配列の行列の表示を行う
続いて行列の表示を行なっていきましょう!
行列の表示は、2次元配列に格納されている値を1行ずつ表示してやることで実現することができます。
スポンサーリンク
行列を1行ずつ表示する
前述の通り、行列を扱う2次元配列を matA
とすれば、matA[i][j]
には行列の成分 \(a_{i+1 j+1}\) が格納されており、\(i\) は \(i\) 行目の成分であることを表す添字で、\(j\) は \(j\) 列目の成分であることを表す添字となります。
したがって、行列の1行目の表示は、下記のように j
を増加させながら printf
で行列の成分を表示してやることで実現することができます(行列の列数を N
としています)。
printf
を使うので、ソースコードの先頭には #include <stdio.h>
が必要になります。
i = 0;
for (j = 0; j < N; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
行列全体を表示する
で、上記の処理を i
を増加させながら全行に対して実行してやれば、行列全体が表示されることになります(行列の行数を M
としています)。
for (i = 0; i < M; i++) {
for (j = 0; j < N; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
例えば行列が下記の \(A\) である場合、
$$ A = \left ( \begin{array}{ccccc} 0 & 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 & 9 \\ 10 & 11 & 12 & 13 & 14 \\ 15 & 16 & 17 & 18 & 19 \end{array} \right ) $$
表示結果は下記のようになります。
0,1,2,3,4, 5,6,7,8,9, 10,11,12,13,14, 15,16,17,18,19,
もし、行の最後にコンマが入るのが嫌であれば、ソースコードを下記のように変更することで、行の最後のみコンマが出力されないようにすることもできます(j
が 列数 - 1
の時は改行を、それ以外の時はコンマの出力を行うように制御しています)。
for (i = 0; i < M; i++) {
for (j = 0; j < N; j++) {
if (j == N - 1) {
printf("%d\n", matA[i][j]);
} else {
printf("%d,", matA[i][j]);
}
}
}
1列ずつ表示しないように注意
行列を表示する際に “1行ずつ” ではなく、”1列ずつ” 表示すると行列が転置してしまうので注意してください。
例えば下記のように行列の表示を行うと、列方向 i
に対するループが先に実行されるため行と列が逆になったように行列が表示されてしまいます。
for (j = 0; j < N; j++) {
for (i = 0; i < M; i++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
例えば行列が下記の \(A\) である場合、
$$ A = \left ( \begin{array}{ccccc} 0 & 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 & 9 \\ 10 & 11 & 12 & 13 & 14 \\ 15 & 16 & 17 & 18 & 19 \end{array} \right ) $$
表示結果は下記のようになります。
0,5,10,15, 1,6,11,16, 2,7,12,17, 3,8,13,18, 4,9,14,19,
割とやりがちな間違いですので、行列の表示を行う際は、”1行ずつ表示する” ことを意識しながら実装するのが良いと思います。
スポンサーリンク
1つの2次元配列で様々な行数・列数の行列を扱う
ここまでは、1つの2次元配列で同じ行数・列数の行列のみを扱うことを前提として解説してきました。
次は、1つの2次元配列でいろんな行数・列数の行列を扱う方法について解説していきたいと思います。
ただ、これは簡単で、まずは今まで下記のように2次元配列のサイズに行数と列数を指定してきたところを、
型 変数名[行列の行数][行列の列数];
下記のように、プログラム内で扱いたい行列の最大の行数と最大の列数を指定するようにします。
型 変数名[行列の最大行数][行列の最大列数];
さらに、実際に扱う行列の行数と列数を、定数ではなく変数で管理するようにします。ここでは、行数を管理する変数を m
、列数を管理する変数を n
とします。
int m, n; /* 実際に扱う行数の行数と列数 */
m = 3;
n = 2;
上記では m = 3
、n = 2
としていますので、3
行 2
列の行数を扱うことになります。
あとは、2次元配列に行列の成分を格納する や 2次元配列の行列の表示を行う で解説してきた内容を、2次元配列の変数宣言時に指定した行数や列数ではなく、m
を行列の行数、n
を行列の列数として処理を行うようにしてやれば良いだけです。
プログラム内で行列の行数と列数を変更する
例えば下記では、プログラム内で扱う行列の最大の行数を 5
、最大の列数を 4
とした行列 matA
を用意していますが、実際には前半では matA
を 3
行 2
列の行列として扱い、後半では matA
を 5
行 4
列の行数として扱っています。
#include <stdio.h>
#define MAX_M 5 /* 扱う行列の最大行数 */
#define MAX_N 4 /* 扱う行列の最大列数 */
int main(void) {
/* 最大行数・最大列数の行列を用意 */
int matA[MAX_M][MAX_N];
int m, n; /* 実際に扱う行数の行数と列数 */
int i, j;
m = 3; /* 行列の行数を3にセット */
n = 2; /* 行列の列数を2にセット */
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("a%d%d = ", i + 1, j + 1);
scanf("%d", &matA[i][j]);
}
}
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
m = 5; /* 行列の行数を5にセット */
n = 4; /* 行列の列数を4にセット */
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
matA[i][j] = n * i + j;
}
}
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
return 0;
}
やってることは単純で、2次元配列としては扱う行列の最大行数(MAX_M
)と最大列数(MAX_N
)分のサイズのものを用意し、
この配列の中の m
で管理される行数分および n
で管理される列数分の部分のみを利用して処理を行なうように制御しているだけです。もっと具体的に言えば、行を表す添字 i
が 0
〜 m - 1
、列を表す添字 j
が 0
〜 n - 1
までしか変化しないように、ループ分で制御を行なっています。
こんな感じで、変数宣言時は大き目のサイズの2次元配列を用意し、行数や列数に応じてその2次元配列の一部分のみを使用するようにしてやれば、行数や列数が配列宣言時のものを超えない限り、1つの2次元配列で様々な行数・列数の行列を扱うことができます。
scanf
で入力された行数と列数の行列を扱う
この方法の良いところは、扱う行列の行数と列数をプログラム記述時ではなく、プログラム実行中に決めることが可能なところです。
例えば下記のように、scanf
でユーザーから入力された行数と列数に応じた行列を扱うようなことも可能です。
#include <stdio.h>
#define MAX_M 100 /* 扱う行列の最大行数 */
#define MAX_N 100 /* 扱う行列の最大列数 */
int main(void) {
/* 最大行数・最大列数の行列を用意 */
int matA[MAX_M][MAX_N];
int m, n; /* 実際に扱う行数の行数と列数 */
int i, j;
/* ユーザーから行数と列数の入力を受け付ける */
printf("行数:");
scanf("%d", &m);
printf("列数:");
scanf("%d", &n);
if (m > MAX_M) {
printf("行数が大きすぎます\n");
return 0;
}
if (n > MAX_N) {
printf("列数が大きすぎます\n");
return 0;
}
/* 以降ではmatAをm行n列の行列として扱う */
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
matA[i][j] = n * i + j;
}
}
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
return 0;
}
もちろん、行列演算によって行列の行数や列数が変化するようなときにも臨機応変に対応することができます。
ただし、2次元配列時に指定した行数や列数を超えた位置の成分にアクセスするとメモリアクセス違反になるので注意してください。
基本的には私のサイトでは下記のように1つの2次元配列で特定の行数・列数の行列のみを扱うことを前提に行列演算の解説をしていますが(一部例外もあります)、必要に応じてこの章で解説した「様々な行数・列数の行列を扱う方法」も利用していただければと思います。
型 変数名[行列の行数][行列の列数];
スポンサーリンク
ポインタで行列を扱う
あとは、ポインタで行列を扱うことも可能で、さらに malloc
関数を利用すれば、必要になったときに必要な行数・列数分だけの行列用のメモリを確保し、不要になったらそのメモリを解放するようなことも可能になります。
ポインタと聞くと難しそうにも感じるかもしれませんが、例えば下記のような関数を利用すれば、2次元配列の時同様に行列を扱うことが可能です(使用する際は stdlib.h
のインクルードが必要です)。
#include <stdlib.h>
/**************************************
* m行n列の行列用のメモリを確保(成分の型はint)
* m:行列の行数
* n:行列の列数
* 返却値:確保したメモリの先頭アドレス(失敗時はNULL)
**************************************/
int **createMatrix(unsigned int m, unsigned int n) {
int **mat;
unsigned int i, j;
if (n == 0 || m == 0) {
printf("n or m is 0\n");
return NULL;
}
/* m行分のポインタ格納用のメモリを確保 */
mat = malloc(sizeof(int *) * m);
if (mat == NULL) {
return NULL;
}
for (i = 0; i < m; i++) {
/* 1行分ずつメモリを確保する */
/* n列分のint型のデータが格納できるメモリを確保 */
mat[i] = malloc(sizeof(int) * n);
if (mat[i] == NULL) {
for (j = 0; j < i; j++) {
free(mat[i]);
}
free(mat);
return NULL;
}
}
return mat;
}
/**************************************
* 行列用のメモリを解放
* m:行列の行数
* 返却値:なし
**************************************/
void deleteMatrix(int **mat, unsigned int m) {
unsigned int i;
for (i = 0; i < m; i++) {
free(mat[i]);
}
free(mat);
}
使い方を簡単に解説しておくと、まず行列を扱う際には、2次元配列ではなく下記のようなポインタのポインタを変数宣言します(createMatrix
関数が int **
にしか対応していないので、型は int **
である必要があります)。
int **mat;
さらに、行列が必要になった際に上記の createMatrix
関数を実行し、その返却値を、上記で宣言したポインタ変数で受け取ります。
/* m行n列の行列用のメモリを確保 */
matA = createMatrix(m, n);
if (matA == NULL) {
printf("メモリ確保エラー\n");
return 0;
}
createMatrix
関数の引数には行数と列数を指定します。
それにより、その行数・列数の行列を扱う際に必要になる分のメモリが確保されます。
関数がエラーになった場合は NULL
が返却されますので、その場合は上記のように以降の処理は行わず、エラー終了する必要があります(メモリ不足・引数に指定した行数や列数が大きすぎるような場合にエラーが出ます)。
関数が成功した場合は、返却値を受け取った変数を2次元配列同様に添字を指定して使用することができるようになります。
例えば scanf で入力された行数と列数の行列を扱う で指定したソースコードは、上記で紹介した関数を利用すれば下記のように書き換えることができます。
#include <stdio.h>
#include <stdlib.h>
/**************************************
* m行n列の行列用のメモリを確保(成分の型はint)
* m:行列の行数
* n:行列の列数
* 返却値:確保したメモリの先頭アドレス(失敗時はNULL)
**************************************/
int **createMatrix(unsigned int m, unsigned int n) {
int **mat;
unsigned int i, j;
if (n == 0 || m == 0) {
printf("n or m is 0\n");
return NULL;
}
/* m行分のポインタ格納用のメモリを確保 */
mat = malloc(sizeof(int *) * m);
if (mat == NULL) {
return NULL;
}
for (i = 0; i < m; i++) {
/* 1行分ずつメモリを確保する */
/* n列分のint型のデータが格納できるメモリを確保 */
mat[i] = malloc(sizeof(int) * n);
if (mat[i] == NULL) {
for (j = 0; j < i; j++) {
free(mat[i]);
}
free(mat);
return NULL;
}
}
return mat;
}
/**************************************
* 行列用のメモリを解放
* m:行列の行数
* 返却値:なし
**************************************/
void deleteMatrix(int **mat, unsigned int m) {
unsigned int i;
for (i = 0; i < m; i++) {
free(mat[i]);
}
free(mat);
}
int main(void) {
/* 行列を指すポインタ */
int **matA;
int m, n; /* 実際に扱う行数の行数と列数 */
int i, j;
/* ユーザーから行数と列数の入力を受け付ける */
printf("行数:");
scanf("%d", &m);
printf("列数:");
scanf("%d", &n);
/* m行n列の行列用のメモリを確保 */
matA = createMatrix(m, n);
if (matA == NULL) {
printf("メモリ確保エラー\n");
return 0;
}
/* 以降ではmatAをm行n列の行列として扱う */
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
matA[i][j] = n * i + j;
}
}
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("%d,", matA[i][j]);
}
printf("\n");
}
/* 行列用のメモリを解放 */
deleteMatrix(matA, m);
return 0;
}
createMatrix
関数実行後は、今までと変わらずに行列が扱えていることが確認できると思います。
ただ、1点注意点があって、行列を使用し終わった際には deleteMatrix
関数を実行する必要があります。createMatrix
関数では malloc
関数を実行してメモリを確保していますので、そのメモリが不要になった際には解放が必要です。それを行なってくれるのが、deleteMatrix
関数となります。
ポインタで行列が扱えた方が便利な場合もありますので、必要に応じて上記の createMatrix
関数や deleteMatrix
関数を参考にしていただければと思います。
ちなみに、createMatrix
関数で使用している malloc
関数については下記ページで解説していますし、
ポインタのポインタについては下記ページで解説していますので、必要に応じてこれらのページも参考にしていただければと思います。
【C言語】ポインタのポインタ(ダブルポインタ)を解説【図解】また、行列のような2次元のデータをポインタで扱う際の考え方については下記ページで解説しており、このページを読んでいただければ createMatrix
関数でどのようなこと行なっているかは理解していただけるのではないかと思います。
まとめ
このページでは、C言語での行列の扱い方について解説しました!
C言語では2次元配列を行列として見なすことで、行列を扱うことができます。
今回は一番基本的な「2次元配列を用いた行列の扱い方」について解説しましたが、ポインタや1次元配列で行列を扱うようなことも可能です。
2次元データを1次元配列やポインタで扱う方法については、先ほども簡単に紹介した下記ページで解説していますので、2次元配列以外で行列を扱いたい場合は下記ページを参考にしていただければと思います。
C言語で2次元データをいろいろな方法で扱ってみる(二次元配列・ポインタのポインタなど)また、今回は単に行列を扱う方法の解説でしたが、行列に対する演算については下記ページで解説していますので、こちらについても是非読んでみてください!
【C言語】行列の「和」と行列の「差」の求め方 【C言語】行列の積の求め方 【C言語】掃き出し法による逆行列の求め方(4×4の逆行列も算出可能)