配列

配列とは

プログラミングで値を一時保存するには変数を使用しますが、一度に大量の変数が必要になる事もあります。
時には100個や1000個などを使用することもありますが、これをひとつずつ変数宣言していたのではコードの記述が大変です。

同じデータ型の変数が複数必要な場合、配列を使用するとコードが簡潔になります。

まずは配列を使用しない場合のサンプルコードから。


static void Main(string[] args)
{
    int num1, num2, num3;
    num1 = 10;
    num2 = 20;
    num3 = 30;

    Console.WriteLine(num1);
    Console.WriteLine(num2);
    Console.WriteLine(num3);

    Console.ReadLine();
}

配列を使用すると、以下のように書くことができます。


static void Main(string[] args)
{
    //配列の宣言
    int[] arr = new int[3];

    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;

    Console.WriteLine(arr[0]);
    Console.WriteLine(arr[1]);
    Console.WriteLine(arr[2]);

    Console.ReadLine();
}

4行目が配列の宣言です。
配列は同じデータ型の変数を任意の数まとめて扱うことができます。

配列の宣言


データ型[] 配列名 = new データ型[要素数];

配列の宣言は、まず変数の時のように扱いたいデータ型を記述します。
その次に角括弧[]を記述します。
これは添字演算子「これから配列を使用する」ということを表します。

続いて、配列名を記述します。
名前は変数名と同じく自由に指定できます。

ここまでで配列の使用宣言になりますが、まだ初期化されていない状態です。
実際に配列の機能を使用するには初期化が必要です。

配列の初期化にはnewキーワードを使用します。
続いて先ほど指定したのと同じデータ型を記述し、[]の中に配列のサイズを数値で指定します。

配列のサイズ(要素数)

配列は複数の値を一括して扱います。
その一つ一つの値のことを要素といいます。

ある配列が持つ要素の数のことを配列のサイズ、配列の要素数と言います。

先ほどのサンプルコードの配列arrは、要素数が3なので、int型のデータを3個保存できます。

配列の要素へのアクセス

作成した配列の各要素に値を代入したり、値を取り出したりするには角括弧[]にアクセスしたい要素の番号を数値で指定します。
この角括弧を添字演算子(インデクサ)と言います。
(添字=そえじ)
添字演算子の中に記述する数値を添え字indexと言います。

配列の添え字は「0」から始まります。
つまり、要素数3の配列を使用宣言した場合、各要素の添え字は「0」「1」「2」の三つとなります。
配列の最後の要素番号は配列のサイズより一つ少ない値になるので注意しましょう。

後は変数と同じように使用することができます。
値の取り出しも代入も変数と同じです。


int[] arr = new int[2];

arr[0] = 999;
arr[1] = arr[0];

Console.WriteLine(arr[0]); //999
Console.WriteLine(arr[1]); //999

配列の初期化

配列は使用する前にその要素数を指定して初期化する必要があります。


int[] arr;
//arr[0] = 1; //初期化していない配列の使用はNG

各要素は、そのデータ型の既定値で初期化されます。
数値型の場合の既定値は「0」です。


int[] arr = new int[3];

//各要素は0で初期化される
Console.WriteLine(arr[0]); //0
Console.WriteLine(arr[1]); //0
Console.WriteLine(arr[2]); //0

既定値についてはデータ型の項で改めて説明します。

配列初期化子

配列は初期化と同時に各要素に値を与えることができます。


int[] arr = new int[3] { 10, 20, 30 };

この配列arrは、宣言と同時に先頭の要素から順に「10」「20」「30」の値が格納された状態になります。
このブロック(波括弧)を配列初期化子または初期化リスト初期化子リストといいます。

配列初期化子の要素数は、配列宣言で指定した通りの要素数を指定する必要があります。
以下はどちらもエラーになります。


//要素数が少ないのでエラー
int[] arr1 = new int[3] { 10, 20 };

//要素数が多いのでエラー
int[] arr2 = new int[3] { 10, 20, 30, 40 };

要素数の省略

配列初期化子を使用して初期化する場合、配列の要素数の指定は省略することができます。


//自動的に要素数は3になる
int[] arr1 = new int[] { 10, 20, 30 };

//自動的に要素数は5になる
int[] arr2 = new int[] { 10, 20, 30, 40, 50 };

配列のサイズは配列初期化子の要素数から自動的に決定されます。
配列初期化子の要素数を増減する場合にコード修正の手間が少なくなるので、配列初期化子を使用する場合は多くの場合で配列宣言の要素数の指定は省略されます。

データ型の省略

配列初期化子を使用する場合、newの後ろのデータ型の指定は省略することができます。


int[] arr1 = new int[] { 10, 20, 30 };

//これでもOK
int[] arr2 = new[] { 10, 20, 30 };

newの省略

配列変数の宣言と同時に配列初期化子を使用する場合、newとその後のデータ型の指定も省略することができます。


int[] arr1 = new int[] { 10, 20, 30 };

//これでもOK
int[] arr2 = { 10, 20, 30 };

この方法は配列の宣言時にしか使用できません。
すでに宣言された配列に代入する場合はnewが必要です。


int[] arr = new int[3];

//エラー
arr = { 10, 20, 30 };

//エラー
arr { 10, 20, 30 };

//newで新しい配列で初期化しなおすのはOK
arr = new int[5] { 10, 20, 30, 40, 50 };
arr = new[] { 10, 20, 30, 40, 50 };

範囲外アクセス

配列の添え字に配列サイズ以上の値を指定したり、マイナス値を指定するとエラーになります。
このエラーはコードの記述時点(コンパイル時点)では検出できず、実行時エラーになります。
(実行時エラー=文法的に間違いではないのでコンパイルはできるが、実際に動かしたときに発生するエラー)


//この配列の添え字の最大値は「2」
int[] arr = new int[] { 10, 20, 30 };

//以下はすべてエラー

//添え字の数値が大きすぎる
arr[3] = 0;

//マイナスは指定できない
arr[-1] = 0;

配列のサイズの取得

配列のサイズを調べるにはLengthプロパティを使用します。


int[] arr = new int[] { 10, 20, 30 };
Console.WriteLine(arr.Length);
3

「プロパティ」とは変数などのオブジェクトが内部的に持っている情報のことです。
変数名に続いて「.」(ドット)で区切ることで内部の情報にアクセスできます。
これはConsole.WriteLineの時と同じですが、WriteLineはメソッド(関数)であるのに対してLengthプロパティはただの数値(int型)であることに注意してください。

配列のLengthプロパティは読み取り専用で、変数のように値を代入することはできません。
(プロパティは全て読み取り専用というわけではなく、書き込みが可能なプロパティもあります)

Lengthプロパティを用いて配列の最後の要素にアクセスするには以下のようにします。


int[] arr = new int[] { 10, 20, 30 };
Console.WriteLine(arr[arr.Length - 1]);
30

この方法は配列の要素数がいくつであっても最後の要素にアクセス可能です。
ただし要素数がゼロの配列の場合は範囲外アクセスになります。

なおLengthプロパティで得られる値はただのint型の値ですから、int型変数に保存しておくこともできます。
他の配列の宣言時のサイズに使用することもできます。


int[] arr1 = new int[] { 10, 20, 30 };

int arr1Length = arr1.Length;

int[] arr2 = new int[arr1.Length];

配列の丸ごとコピー?

配列の各要素は通常の変数と同じように扱うことができますが、「配列」自体、つまり添え字を使用しない場合はやや特殊な扱いとなります。

例えば配列を丸ごとコピーしようとして、以下のように配列変数に別の配列変数を代入したとします。


static void Main(string[] args)
{
    int[] arr1 = new int[] { 10, 20, 30 };
    int[] arr2 = new int[arr1.Length]; //arr1と同じサイズの配列を作る

    arr2 = arr1;

    Console.WriteLine(arr2[0]);
    Console.WriteLine(arr2[1]);
    Console.WriteLine(arr2[2]);

    Console.ReadLine();
}
10
20
30

一見うまく動作しているようにも見えますが、これはほとんど意味のないコードです。
このコードの配列arr2は、配列arr1の各要素の値はコピーされておらず、配列arr1そのものになってしまっているのです。

試しにコピー元の配列arr1の値を書き換えてみます。


static void Main(string[] args)
{
    int[] arr1 = new int[] { 10, 20, 30 };
    int[] arr2 = new int[arr1.Length];

    arr2 = arr1;

    Console.WriteLine(arr2[0]);
    Console.WriteLine(arr2[1]);
    Console.WriteLine(arr2[2]);
    Console.WriteLine(); //改行

    //arr1の要素を書き換え
    arr1[0] = 999;

    Console.WriteLine(arr2[0]);
    Console.WriteLine(arr2[1]);
    Console.WriteLine(arr2[2]);

    Console.ReadLine();
}
10
20
30

999
20
30

arr2arr1を代入した後はarr2には手を加えていません。
その後にarr1[0]の値を書き換えると、arr2[0]の値も変わっていることがわかります。
つまり、配列に別の配列を代入すると、各要素のコピーではなく代入した配列の別名になってしまうのです。
それを理解した上で代入するのならば構いませんが、各要素のコピーが欲しいのならばこの方法は使えません。

なぜこのような動作になるかは値型と参照型で解説しています。

要素の中身ごとコピー

以下はまだ説明していない機能を使用しているので、参考程度にみてください。

要素の丸ごとコピーをしたいのならば各要素をひとつずつ代入していくのが最も単純な方法です。
数が多いと大変ですが、for文などのループ文を使用すれば簡単に記述可能です。


int[] arr1 = new int[] { 10, 20, 30 };
int[] arr2 = new int[arr1.Length];

for (int i = 0; i < arr1.Length; i++)
{
    arr2[i] = arr1[i];
}

そのほか、専用の関数を使用する方法もあります。


static void Main(string[] args)
{
    int[] arr1 = new int[] { 10, 20, 30 };
    int[] arr2 = new int[arr1.Length];

    //配列のすべての要素のコピー
    Array.Copy(arr1, arr2, arr2.Length);

    Console.WriteLine(arr2[0]);
    Console.WriteLine(arr2[1]);
    Console.WriteLine(arr2[2]);

    arr1[0] = 999;

    Console.WriteLine();

    Console.WriteLine(arr2[0]);
    Console.WriteLine(arr2[1]);
    Console.WriteLine(arr2[2]);

    Console.ReadLine();
}
10
20
30

10
20
30

arr1の要素の書き換えがarr2には影響していないことがわかります。
arr2arr1とは独立した配列になったためです。

.NET Framework3.5以上ならば以下の方法が最も簡単です。
(最近のバージョンならば使用可能)


using System;
using System.Linq; //←これが必要(無ければ追加)

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums1 = new int[] { 1, 2, 3 };

            //nums1のコピー
            int[] nums2 = nums1.ToArray();
        }
    }
}