配列( array )

  1. 1 次元配列

      配列として宣言された変数( x1,x2 )は,ポインタ( x3, x4 )のような働きをしますが,配列変数をポインタへ代入する( 05 行目),ポインタを他のポインタに代入する( 12 行目)のは可能であっても,配列変数を他の配列変数に代入すること( x2 = x1 など)は許されない,x3++ は許されるが x1++ は許されない,など,多少ポインタとは異なります.
    01	#include <stdio.h>
    02	int main()
    03	{
    04		int x1[] = {1, 2, 3}, x2[3], *x3, *x4 = new int [3];
    05		x3 = x2;   // x3 = &x2[0];
    06		for (int i1 = 0; i1 < 3; i1++) {
    07			x2[i1] = x1[i1];   // x2 = x1; は許されない
    08			x4[i1] = 10 * (i1 + 1);
    09		}
    10		for (int i1 = 0; i1 < 3; i1++)
    11			printf("i1 %d x1 %d x2 %d x3 %d x4 %d\n", i1, x1[i1], x2[i1], x3[i1], x4[i1]);
    12		x3 = x4;
    13		for (int i1 = 0; i1 < 3; i1++)
    14			printf("i1 %d x1 %d x2 %d x3 %d x4 %d\n", i1, x1[i1], x2[i1], x3[i1], x4[i1]);
    15		return 0;
    16	}
    			
    04 行目

      配列 x1,x2 を定義し,x1 に対しては初期設定を行っています.また,x3,x4 をポインタ変数として定義し,x4 には,3 つ の int 型データを記憶できる領域を確保し,そのアドレスで初期設定を行っています.この結果,確保された領域のデータを,x4[i] ( i = 0, 1, 2 )のように,配列変数と同じような参照が可能になります.

    05 行目

      x2 を x3 に代入しています.x2 は配列変数ですが,ポインタと非常に似た働きをします.この代入は,コメントに書かれたようなことを意味し,x2 と x3 が同じ配列を指すことになります.その結果,x2 の要素の値を変更すれば,x3 の対応する要素の値も変化します(逆も同様)

    06 行目~ 09 行目

      「 x2 = x1; 」のような操作は許されないため,07 行目において,x1 の各要素の値を x2 の対応する要素に代入しています.また,x4 が指す領域の値を設定しています( 08 行目).この結果,10,11 行目の文によって,以下に示すような結果が得られます.また,概念的には,その下に示した図のような状態になります.
    	i1 0 x1 1 x2 1 x3 1 x4 10
    	i1 1 x1 2 x2 2 x3 2 x4 20
    	i1 2 x1 3 x2 3 x3 3 x4 30
    				

    12 行目

      x4 を x3 に代入しています.その結果,x3 と x4 が同じ配列を指すことになり,x3 の要素の値を変更すれば,x4 の対応する要素の値も変化します(逆も同様).13,14 行目の文によって,以下に示すような結果が得られ,概念的には,その下に示した図のような状態になります.
    	i1 0 x1 1 x2 1 x3 10 x4 10
    	i1 1 x1 2 x2 2 x3 20 x4 20
    	i1 2 x1 3 x2 3 x3 30 x4 30
    				

      また,以下のプログラムに示すように,複数のデータを記憶するために配列を使用しても,標準関数 malloc や new 演算子を使用しても,上で述べた点を除き,各要素の参照方法はほとんど同じです.ただし,確保可能な領域の大きさは異なってくる可能性があります(コンパイラやコンピュータによって異なる可能性がある).例えば,以下に示すプログラムの断片において,配列を使用する場合は正しく動作せず,malloc や new 演算子を使用する場合は正しく動作します.これは,new 演算子を使用した場合,その領域が,プログラム実行時に任意に確保や解放を繰り返すことができるメモリ領域(ヒープ領域,ヒープメモリ,heap memory )に確保されるからです.
    //	int x[5000000];   // 実行できない
    //	int *x = (int *)malloc(5000000 * sizeof(int));   // 実行可
    	int *x = new int [5000000];   // 実行可
    	x[4999999] = 100;
    	printf("%d\n", x[4999999]);
    			
  2. 2 次元配列

      多次元配列の場合も,1 次元配列の場合と同様,配列を使用しても,new 演算子を使用しても,各要素の参照方法に関する限りほとんど同じです.大きな違いは,連続した領域が確保されるか否かといった点です.連続した領域が確保されるか否かといった問題は,プログラムの実行時間に大きな影響を与える場合があります(連続領域の方が実行時間が短い).
    01	#include <stdio.h>
    02	int main()
    03	{
    04		int x1[][2] = {{1, 2}, {3, 4}, {5, 6}}, *x2, **x3, **x4;
    05		x2 = &x1[0][0];   // x2 = x1[0]; でも可
    06	//		m 行 n 列の (i, j) 要素の位置: i * n + j
    07		for (int i1 = 0; i1 < 3; i1++) {
    08			for (int i2 = 0; i2 < 2; i2++)
    09				printf("x1 %d x2 %d\n", x1[i1][i2], x2[i1*2+i2]);
    10		}
    11		x3 = new int *[3];
    12		for (int i1 = 0; i1 < 3; i1++)
    13			x3[i1] = new int [2];
    14		x4 = x3;
    15		x3[0][0] = 10;
    16		x3[0][1] = 20;
    17		x3[1][0] = 30;
    18		x3[1][1] = 40;
    19		x3[2][0] = 40;
    20		x3[2][1] = 50;
    21		for (int i1 = 0; i1 < 3; i1++) {
    22			for (int i2 = 0; i2 < 2; i2++)
    23				printf("x3 %d x4 %d\n", x3[i1][i2], x4[i1][i2]);
    24		}
    25	//		以下は許されない(x3[0][0],x3[0][1] だけは参照できる)
    26		int *x5 = &x3[0][0];   // int *x5 = x3[0]; でも可
    27		for (int i1 = 0; i1 < 3; i1++) {
    28			for (int i2 = 0; i2 < 2; i2++)
    29				printf("x3 %d x5 %d\n", x3[i1][i2], x5[i1*2+i2]);
    30		}
    31		return 0;
    32	}
    			
    04 行目

      3 行 2 列の 2 次元配列 x1,及び,3 つのポインタ変数 x2,x3,x4 を定義しています.初期設定を行う場合,最も左側の要素数だけは省略可能です(省略しなくても構わない).

    05 行目

      配列宣言を行った場合,全ての要素は連続した領域に確保されるため,その全体,または,一部を 1 次元配列として扱うことができます.x2 には,連続領域の先頭アドレスが代入されるため,x2 は,要素数が 6 個の 1 次元配列となります.また,x2 = $(x1[1][1]) のような代入を行えば,変数 x2 は,要素数が 3 である 1次元配列となります.勿論,x2 は,x1 と同じ領域を指しているため,x2 を介して要素の値を変更すれば,対応する x1 の要素の値も変化します(逆も同様).この行を実行した結果,概念的には,右図のような結果になります.

    07 行目~ 10 行目

      x1 及び x2 の値を出力しています.06 行目のコメントに記したように,m 行 n 列の 2 次元配列を 1 次元配列としてみた場合,2 次元配列の i 行 j 列要素は,1 次元配列の i * n + j 要素に対応します.出力結果は以下に示すとおりです.
    	x1 1 x2 1
    	x1 2 x2 2
    	x1 3 x2 3
    	x1 4 x2 4
    	x1 5 x2 5
    	x1 6 x2 6
    				
    11 行目

      int 型に対するポインタを記憶する領域を 3 個確保し( x3[0],x3[1],x3[2] ),その先頭アドレスを x3 に代入しています.

    12 行目~ 13 行目

      int 型データを記憶する領域を 2 個確保し,そのアドレスを,11 行目で確保したポインタを記憶する各領域( x3[i1] )に代入しています.この結果,概念的に,右図のような状態になります( x4,x5 の部分,及び,10,20,・・・ など,各要素の値は除く)

    14 行目

      この代入によって,x3 と x4 は,同じ 2 次元配列となります.また,15 行目~ 24 行目の文によって,以下に示すような出力が得られます.
    	x3 10 x4 10
    	x3 20 x4 20
    	x3 30 x4 30
    	x3 40 x4 40
    	x3 40 x4 40
    	x3 50 x4 50
    				
    26 行目

      x5 に x3[0][0] のアドレスを代入しています.new 演算子を使用して配列を確保した場合,通常の配列の場合とは異なり,全てのデータが連続した領域に確保されるとは限りません.そのため,配列全体を 1 次元配列として処理することは不可能です.この場合,x3 の 1 行目だけは正しく扱えますが,下に示す出力結果からも明らかなように,2 行目以降は,意味が無い出力となってしまいます(このような参照は避けるべきです).この例の場合,int *x5 = &x3[0][0];,int *x5 = &x3[1][0];,int *x5 = &x3[2][0]; などの方法によって,各行を要素数が 2 である 1 次元配列として扱うことが可能です.
    	x3 10 x5 10
    	x3 20 x5 20
    	x3 30 x5 1628620015
    	x3 40 x5 134280023
    	x3 40 x5 12594745
    	x3 50 x5 0