配列

スポンサーリンク

1 次元配列

次のプログラムは,要素数 3 の文字の配列を扱った例です。

#include <stdio.h>

int main(void)
{
    char arr[3];  /* 要素数 3 の文字の配列を宣言 */

    arr[0] = 'a';
    arr[1] = 'b';
    arr[2] = 'c';

    printf("%c\n", arr[0]);  /* 出力: a */
    printf("%c\n", arr[1]);  /* 出力: b */
    printf("%c\n", arr[2]);  /* 出力: c */

    return 0;
}

一般に,配列の宣言は次のように書きます。

型名  配列名[要素数];

要素数はコンパイル時定数で指定しなければなりません。
要素数がプログラム実行時に決まる場合は,後述する malloc 関数を用いて配列を作成します。

要素数 3 の配列を宣言したとき,配列の各要素には arr[0], arr[1], arr[2] のようにアクセスします。

配列に入る要素が最初からすべてわかっている場合,次のように書くこともできます。
波括弧 { ... } の中に,要素をコンマで区切って順番に並べます。
なお,この書き方は配列の初期化時のみに有効です (代入には使えません)。

#include <stdio.h>

int main(void)
{
    char arr[] = { 'a', 'b', 'c' };

    printf("%c\n", arr[0]);  /* 出力: a */
    printf("%c\n", arr[1]);  /* 出力: b */
    printf("%c\n", arr[2]);  /* 出力: c */

    return 0;
}

配列を引数とする関数は次のように書けます。
関数の呼び出し時に配列の複製は行われません。

#define SIZE 3

void init(int a[SIZE])
{
    int i;
    for (i = 0; i < SIZE; i++) a[i] = 0;
}

int main(void)
{
    int arr[SIZE];
    init(arr);
    return 0;
}

多次元配列

次のプログラムは,文字の 2 次元配列を扱った例です。
これと同様にして,3 次元以上の配列を作ることもできます。

#include <stdio.h>

int main(void)
{
    char arr[][3] = {
        { 'A', 'B', 'C' },  /* arr[0][x] の成分 */
        { 'a', 'b', 'c' }   /* arr[1][x] の成分 */
    };

    printf("%c\n", arr[0][0]);  /* 出力: A */
    printf("%c\n", arr[0][1]);  /* 出力: B */
    printf("%c\n", arr[0][2]);  /* 出力: C */

    printf("%c\n", arr[1][0]);  /* 出力: a */
    printf("%c\n", arr[1][1]);  /* 出力: b */
    printf("%c\n", arr[1][2]);  /* 出力: c */

    return 0;
}

配列宣言時に初期化する場合,要素数の指定を省略できるのは最上位だけです。

配列とポインタ

例えば int arr[3] という配列を宣言したとき,配列名 arr はその配列を表すポインタとなります。
ポインタ arr の値は,配列の先頭要素のアドレス &arr[0] となります。

int arr[3] = { 123, 456, 789 };

printf("%d\n", &arr[0]);  /* 出力: (先頭要素のアドレス) */
printf("%d\n", arr);      /* 出力: (先頭要素のアドレス) */

printf("%d\n", arr[0])    /* 出力: 123 */
printf("%d\n", *arr);     /* 出力: 123 */

配列のアドレスと値の対応

int 型のサイズが 4 バイトの処理系では,配列 int arr[3] の各要素は 4 バイト間隔で順番に並びます (右図)。
このとき,arr の値が 1000 であれば,arr + 1 の値は 1004, arr + 2 の値は 1008 と計算されます。

int arr[3] = { 123, 456, 789 };

printf("%d\n", &arr[0]);     /* 出力例: 1000 */
printf("%d\n", &arr[1]);     /* 出力例: 1004 */
printf("%d\n", &arr[2]);     /* 出力例: 1008 */

printf("%d\n", arr);         /* 出力例: 1000 */
printf("%d\n", arr + 1);     /* 出力例: 1004 */
printf("%d\n", arr + 2);     /* 出力例: 1008 */

printf("%d\n", *arr);        /* 出力例: 123 */
printf("%d\n", *(arr + 1));  /* 出力例: 456 */
printf("%d\n", *(arr + 2));  /* 出力例: 789 */

配列名はその配列専用のポインタであるため,他の配列のアドレスを代入することはできません。

int a[3], b[3];
b = a;      /* 不可 */

ポインタ型の互換性

次のプログラムは,普通の整数型のポインタ ptr を用いて,配列 arr にアクセスする例です。

int arr[3] = { 123, 456, 789 };
int *ptr = arr;

printf("%d\n", arr);         /* 出力: (先頭要素のアドレス) */
printf("%d\n", ptr);         /* 出力: (先頭要素のアドレス) */

printf("%d\n", arr[1]);      /* 出力: 456 */
printf("%d\n", ptr[1]);      /* 出力: 456 */

printf("%d\n", *(arr + 2));  /* 出力: 789 */
printf("%d\n", *(ptr + 2));  /* 出力: 789 */

arr の型は int [3],ptr の型は int * ですが,両者はほとんど同じ振る舞いをします。

int [3] 型と int * 型は,完全に互換ではありません。
例えば,sizeof で arr と ptr のサイズを調べると,異なる値が得られます。

int arr[3] = { 123, 456, 789 };
int *ptr = arr;

printf("%d\n", sizeof arr);   /* 出力: 12 */
printf("%d\n", sizeof ptr);   /* 出力: 4 */

多次元配列とポインタ

配列のアドレスと値の対応

次のプログラムは,右図の 2 x 3 多次元配列を扱ったものです。
図から明らかなように,int [2][3] 型と互換性のあるポインタ型は int * 型であり, int ** 型とは互換性がありません。

#define X 2
#define Y 3

int arr[X][Y] = { { 1, 3, 5 }, { 2, 4, 6 } };
int
int *ptr = arr;

printf("%d\n", arr[1][2]);           /* 出力: 6 */
printf("%d\n", ptr[1][2]);           /* 不可 */
printf("%d\n", *(ptr + 1 * Y + 2));  /* 出力: 6 */

ポインタの配列

次のプログラムは,右図のような構成をした,いわば配列の配列を扱ったものです。
最上位の配列は int *[2] 型であり,これと互換性のあるポインタ型は int ** 型です。

int a[3] = { 1, 3, 5 };
int b[3] = { 2, 4, 6, 8 };
int *arr[2] = { a, b };
int **ptr = arr;

printf("%d\n", arr[1][2]);  /* 出力: 6 */
printf("%d\n", ptr[1][2]);  /* 出力: 6 */

このように,多次元配列では配列の構成方法によって利用可能なポインタ型が異なることに注意が必要です。

スポンサーリンク