2次元配列
普通の2次元配列
まず,静的に配列を確保する場合,m 行 n 列の要素をもつ2次元配列 d は,
double d[m][n];
として宣言する(d[m,n]
ではないことに注意).この場合,d は,「要素数n個の配列」を要素に持つ配列,となる.
つまり d[0]
自体が,n 個の要素を持つ配列であり,その n 個目の要素に対して d[0][n]
としてアクセスするということになる.<p>
この配列を関数の引数として渡すためには,
void foo(double (*d)[n]);
あるいは
void foo(double d[][n]);
というように,「要素数n個の配列」を要素に持つ配列ですよ,として渡す必要がある$n$ の部分には具体的な数字が入る.変数ではダメ).
また,動的に確保するためには,同様にして,
double (*d)[n];
d = (double (*)[n])calloc( n * m, sizeof(double) );
として確保することになる(上と同様, n の部分には具体的な数字が入る.変数ではダメ). すなわち,まず,「n 個の要素を持つ配列,へのポインタ」を定義しておき,「n 個の要素を持つ配列」× m 個のメモリを確保する.
これよりわかるように,通常の2次元配列は,列数 n があらかじめわかっていないと,引数として渡すことも,メモリを動的確保することもできない.
行数,列数ともに未知の配列
では,行数,列数ともに未知の場合には,どうすればよいかというと,
- 1次元配列を使う(講義資料のメインのコードはこの方式を使っている).
- 1次元配列へのポインタ配列を使う.
のいずれかにより処理することになる.
1番目のケースは,本文中にあるように,2次元配列として確保するかわりに,m x n 個の要素をもった1次元配列を確保する,というものである.この場合は,i 行 j 列の要素に対して,d[n*(i-1)+(j-1)] としてアクセスすることになる.<p>
2番目のケースは具体的には次のようなものである.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
double **matrix_alloc(int row, column) {
double **a; /* もしくは,double *a[] */
int i;
a = (double**)calloc(row, sizeof(double*));
if (a == NULL) {
fprintf(stderr, "Cannot allocate memory.\n");
exit(1);
}
for (i = 0; i < row; i++) {
a[i] = (double *)calloc(column, sizeof(double));
if (a[i] == NULL) {
fprintf(stderr, "Cannot allocate memory.\n");
exit(1);
}
}
return a;
}
5行目の a = (double**)calloc(row, sizeof(double*))
で,double配列へのポインタとして,row 個の要素をもった double ** 型の配列が確保される.次に,12行目の a[i] = (double *)calloc(column, sizeof(double))
で,要素数 column の double 型配列が確保され,そのアドレスが,先に確保したポインタ配列 a に代入される.
別の言い方をすれば,m×nの行列を確保する場合,各行ごとに,要素数 m 個の配列を動的確保する(n 行あるので,n 回確保する).そして,これら n 個の行を一つに束ねるために,「要素数 m 個の配列」を n個分,要素として持つ配列,を動的に確保する(実際のプログラムでは手順が逆だけど)という作業になる.
この方式の場合,メモリ上には,要素 m 個の配列が n 個と,それらへのポインタを要素に持つ要素数 n 個の配列とが,確保されることになる.そのため,配列の大きさ次第では,メモリの無駄が大きい.しかし,通常の2次元配列の場合と同様に d[i-1][j-1]
としてアクセスすることが可能である.
メモリの解放
動的に確保された2次元配列を解放するときは,メモリを確保したときと,逆の手順で一つずつ解放する必要がある.すなわち,
- 各行の配列を解放し,
- 全体を束ねる配列を解放する.
という手順が必要である.解放の方法は以下のコードを参考にすること.
void matrix_free(double **mat, int rows, int column) {
int i;
for (i = 0; i < rows; i++) {
free(mat[i]);
}
free(mat);
}
2次元配列と1次元配列,どっちが良い?
構造体等で専用のタイプを設計し,それに付随する関数/マクロ群を定義してしまうのであれば,結局,どちらのデータ構造を使っても使い勝手には大差はない. ただし,現代的なコンピュータでは一般に1次元配列を使う方が全体的な計算効率が良くなる傾向になる.
安直に考えれば,1次元配列を使う方法で要素を参照する際,番地を得るために乗算 + 加算を行うのに対し, 本付録で示した2次元配列では,間接参照が1回行われるだけである.そのため2次元配列の方が,一般に高速に動作するように感じる. しかしながら,現在のコンピュータは演算速度が十分に早く,メモリ参照のコストの方が相対的に大きくなっている. 特に1次元配列は全ての要素が連続したメモリに確保されるため,順々に要素にアクセスするような一般的な使い方において, メモリ領域が断片的になっている2次元配列よりも高速に動作する.
また,2次元配列では,ポインタの配列1個分の場所が余分に必要になるため,1次元配列より多くのメモリを必要とする.これらを踏まえると,2次元配列を使うメリットは要素の書き方が直感的である以外には,ほとんどないことになる.