二次元配列の動的確保
Copyright(C) 14May2003 coskx
大きな二次元配列や、プログラムが起動するまで、大きさの決まらない二次元配列の取り方を解説する。
実際には二次元配列ではなく、動的に確保された一次元配列の先頭アドレスを格納するポインタの配列を用いて、二次元配列のように扱えるようにするという意味となる。
要点 ポインタを用いて,一度動的確保を行なうと,最初から宣言された2次元配列のように扱える。
1.ポインタ変数の配列を用いたint型二次元配列確保の例 | |
List 1 二次元配列の動的確保のプログラム |
#include <stdio.h> |
実行結果 |
0 1 2
3 4 5 6 10 11 12 13 14 15 16 20 21 22 23 24 25 26 30 31 32 33 34 35 36 |
int
**array2d:
array2dは,「「int型変数のアドレスを格納するポインタ変数」のアドレス」を格納するポインタ変数の意味である。
あるいは **array2は「「array2に格納されている値をアドレスとするポインタ変数」に格納されているアドレス」に格納されているint型変数の値を表すと言っても良い.
この変数int
**array2dは次のように使われる。
int型変数のアドレスを格納するポインタ変数を指すポインタ変数 array2d | |
値 |
アドレスa |
int型変数を指すポインタ変数 名前はないがアドレスaに存在 | |
値 |
アドレスb |
int型変数 名前はないがアドレスbに存在 | |
値 |
整数値 |
malloc(sizeof(int
*)*row):
int型のアドレスを格納するポインタのサイズでrow個のメモリを確保してその先頭番地を返す関数呼び出し
(int **)malloc(sizeof(int
*)*row):
先頭番地を受け取ったらそれはポインタ変数の配列なので、ポインタ変数のアドレスをしまうポインタ変数型へキャスト
malloc(sizeof(int)*column):
int型のサイズでcolumn個のメモリを確保してその先頭番地を返す関数呼び出し
メモリ確保終了時の擬似二次元配列は次のような構造になっている
ポインタ変数を指すポインタ変数 array2d | |
値 |
アドレスa |
int型変数を指すポインタ変数の配列 名前はないがアドレスaに存在 | ||||
要素番号 |
0 |
1 |
2 |
3 |
値 |
アドレスb |
アドレスc |
アドレスd |
アドレスe |
int型変数配列 名前はないがアドレスbに存在 | |||||||
要素番号 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
値 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
int型変数配列 名前はないがアドレスcに存在 | |||||||
要素番号 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
値 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
int型変数配列 名前はないがアドレスdに存在 | |||||||
要素番号 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
値 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
int型変数配列 名前はないがアドレスeに存在 | |||||||
要素番号 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
値 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
未定 |
2.ポインタ変数の配列を用いた汎用型二次元配列確保の例 | |
次の例は,いろいろな型の二次元配列の動的確保のプログラム例である。関数openArray(),関数closeArray()は読みにくいので,最初はこれらの関数の中身を理解しないで使ってみる方がよい。main()などからいろいろな型の変数の二次元配列(動的確保)を使う方法をこのプログラムの関数main()を見て修得しよう。使い方を理解すれば細かいことは考えなくても使える。構造体の二次元配列も作れるはずである。
次の段階として,関数openArray(),関数closeArray()の作り方を理解しないと気持ち悪いと考えたら,じっくりと理解しよう。
List 2 二次元配列の動的確保のプログラム |
#include <stdio.h> |
実行結果 |
0 1
2 3 4 5 6 10 11 12 13 14 15 16 20 21 22 23 24 25 26 30 31 32 33 34 35 36 1.0000 0.5000 0.3333 0.2500 0.0909 0.0833 0.0769 0.0714 0.0476 0.0455 0.0435 0.0417 0.0323 0.0313 0.0303 0.0294 0.0244 0.0238 0.0233 0.0227 0.0196 0.0192 0.0189 0.0185 |
array2d=(int
**)openArray(4,7,sizeof(int)):
「int型で4×7の配列を確保し,その配列の先頭アドレスを返してもらって,aray2dに保存」と読めばよい。
「int
array2d[4][7];」が使えるようになったと思えばよい。
array2dd=(double
**)openArray(6,4,sizeof(double)):
「double型で6×4の配列を確保し,その配列の先頭アドレスを返してもらって,aray2ddに保存」と読めばよい。
「double
array2dd[6][4]」が使えるようになったと思えばよい。
「void型のポインタ」には,特定の型の変数のアドレスではなく,アドレスなら何でも保存できる便利なポインタ変数である。しかし,これはアドレスの保存用に使われるだけで,そのアドレスが指す値を参照したり書き換えるには別のポインタ変数に代入したり,キャストしなければならない。
もし次のような構造体の配列を20×8確保したいのなら次のようにするとよい。
typedef struct
{
int xxxxx;
int
yyyyy;
char sssss[16];
} mystruct_t;
mystruct_t
**structarray2d;
structarray2d==(mystruct_t
**)openArray(20,8,sizeof(mystruct_t)):
3.行列とベクトルの例 (どうしてポインタが配列として扱えるのか) | |
行列やベクトルを変数として扱う時は,上記の方法を使うと行列やベクトルの次元を後から決まることができる。
次のプログラムは,どうしてメモリを確保したポインタが配列として扱えるかがわかる例である。
要点 ptrをポインタ変数(たとえばdouble *ptr)とすると 「*(ptr+3)」と「ptr[3]」はまったく同じ機械語に変換される。 |
「*(ptr+3)」と「ptr[3]」と「*(3+ptr)」と「3[ptr]」
は同じ変数をさすことになる。
「*(ptr+3)」は「{ptr(アドレス値)+3×その型の変数の占有するバイト数}の指す変数}の意味
「ptr[3]」も「{ptr(アドレス値)+3×その型の変数の占有するバイト数}の指す変数}の意味
「*(3+ptr)」は「{3×その型の変数の占有するバイト数+ptr(アドレス値)}の指す変数}の意味
「3[ptr]」も「{3×その型の変数の占有するバイト数+ptr(アドレス値)}の指す変数}の意味
なのでこの4つは同じ変数をさすことになる。
同様にpptrをポインタのポインタ変数(たとえば
double **pptr)とすると
「pptr[2][3]」と「*(*(ptr+2)+3)」は同じになる。
List 3 行列とベクトルのプログラム |
#include <stdio.h> double **createMatrix(int dimension) void deleteMatrix(double **matrix, int
dimension) void deleteVector(double vector[]) double *createVector(int dimension) int main() |
実行結果 |
*(*(mat+1)+2) =3.000000 |