arrayクラス

配列に代わる機能1

C言語では同じデータ型の変数をたくさん扱う場合には配列を使用します。
C++でも配列は使用しますが、データの集合をより便利に扱えるコンテナクラス(コンテナ型)を使用することも多いです。

コンテナクラスはSTL(Standard Template Library)と呼ばれるものの一部です。
STLはテンプレート機能(関数テンプレートと同じ)を利用したライブラリで、特定のデータ型に依存しない機能を提供します。

コンテナ型のひとつであるarrayクラスは基本的に通常の配列と同じ物ですが、配列よりも便利な機能が用意されています。
記述方法さえ覚えてしまえばほとんど同じものとして扱えます。

なお、arrayクラスはC++11以降で使用できます。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr{1, 2, 3, 4, 5};
    for (size_t i = 0; i < arr.size(); i++)
    {
        std::cout << arr[i] << "\n";
    }

    std::cin.get();
}

arrayクラスを使用するには#include <array>を記述します。
arrayクラスはstd名前空間に属します。

宣言と初期化

arrayクラスの使用の宣言は以下のようにします。


std::array<int, 5> arr;

これは「int型で要素数が5」のarrayクラスの使用を宣言しています。
宣言だけで初期化をしない場合、要素の値は不定です。

宣言と同時に初期化するには以下のようにします。


std::array<int, 5> arr1{ 1, 2, 3, 4, 5 };
std::array<int, 5> arr2 = { 1, 2, 3, 4, 5 };

//足りない分は0で埋められる
std::array<int, 5> arr3{ 1, 2 }; 

//以下はエラー

//初期化子が多すぎる
std::array<int, 5> arr4{ 1, 2, 3, 4, 5, 6 };

//要素数の省略はできない
std::array<int> arr5{ 1, 2, 3, 4, 5 };

要素数の指定に対して初期化子が少ないと、残りは0で埋められます。
要素数の指定に対して初期化子が多すぎるとエラーになります。
これらは配列と同じです。

配列と違うのは、初期化子を指定しても要素数の記述を省略できない点です。
配列と比べてこれは不便と言えるかもしれません。

また、別のarrayクラスを利用して初期化することもできます。


std::array<int, 5> arr1{ 1, 2, 3, 4, 5 };

//arr1でarr2を初期化
std::array<int, 5> arr2(arr1);
//↓もOK
//std::array<int, 5> arr2 = arr1;

//コピーなので元が変更されても影響しない
arr1[0] = 10;

//1を表示
std::cout << arr2[0];

この方法では、arr1の各要素がarr2にそのままコピーされます。
この時、初期化に利用できるのはデータ型と要素数が同じarrayクラスに限られます。

ちなみにクラス型の変数はインスタンスといいます。
上のコードではarr1arr2などがインスタンスです。
これはクラスの項で改めて説明します。

代入操作

arrayのインスタンス同士はそのまま代入によるコピーが可能です。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr1{ 1, 2, 3, 4, 5 };
    std::array<int, 5> arr2;

    //arr2にarr1の要素をすべてコピー
    arr2 = arr1;

    //コピー元を書き換えてもコピー先に影響しない
    arr1[0] = 10;
    std::cout << arr2[0] << std::endl;

	//初期化子リストで代入操作が可能
    arr1 = { 6, 7, 8 };

    std::cin.get();
}

全ての要素がコピーされるので、コピー元を書き換えてもコピー先には影響しません。
ただし、代入できるのはデータ型と要素数が同一のarrayインスタンス同士のみです。

また、配列の場合は初期化子リスト({})は宣言時の初期化にしか使用できませんでしたが、arrayクラスではすでに宣言したarrayインスタンスに値を再代入することができます。

各要素へのアクセス

各要素の値の取り出しや値の代入方法は配列と同じです。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr{ 1, 2, 3, 4, 5 };

    arr[1] = 10;

    //10
    std::cout << arr[1] << std::endl;

    std::cin.get();
}

特に難しい点はないでしょう。

stringクラスの時と同様に、より安全なat関数によるアクセス方法も存在します。
しかし要素数が固定のarrayクラスでは使用するメリットはあまりありません。

二次元配列

二次元配列として使用する場合は以下のようにします。


#include <iostream>
#include <array>

int main()
{
    using namespace std;

    int nums[2][3] = {
        { 1, 2, 3 },
        { 4, 5, 6 }
    };

    //↑と同等
    array<array<int, 3>, 2> arr{ {
        { 1, 2, 3 },
        { 4, 5, 6 }
    } };

    for (size_t i = 0; i < arr.size(); i++)
    {
        for (size_t j = 0; j < arr[i].size(); j++)
        {
            cout << arr[i][j] << " ";
        }
        cout << "\n";
    }

    cin.get();
}

配列の場合とは要素数の指定の順序が逆になります。
また、配列の初期化子をさらに波括弧で括る形になります。

配列と比べてusing namespace std;を使用してもコード記述量が少し多く、また要素の並び順がやや直感的でないのが難点です。

関数

arrayクラスには便利な関数(メンバ関数)が多数存在します。
その中でよく使いそうなものをいくつかピックアップします。

size関数

arrayの要素数を得るにはsize関数を使用します。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr{ 1, 2, 3, 4, 5 };
    for (size_t i = 0; i < arr.size(); i++)
    {
        std::cout << arr[i] << "\n";
    }

    std::cin.get();
}

通常の配列の場合はsizeof(arr) / sizeof(arr[0])という風に要素数を計算する必要がありましたが、arrayクラスでは簡単に取得できるようになっています。
ちなみに戻り値はsize_t型です。

empty関数

arrayが空か否かを判定するにはempty関数を使用します。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 0> arr1;
    std::array<int, 5> arr2{ 1, 2, 3, 4, 5 };

    std::cout << arr1.empty() << std::endl;
    std::cout << arr2.empty() << std::endl;

    std::cin.get();
}

空の場合はtrueを、それ以外の場合はfalse以外を返します。

空のarrayクラスを宣言することはほとんどないので使い道はありませんが、他のコンテナ型ではあり得ることで、統一性を持たせるために用意されています。

front関数、back関数

array要素の取得、設定にはfront関数、back関数を使用する方法もあります。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr{ 1, 2, 3, 4, 5 };

    std::cout << arr.front() << std::endl;
    std::cout << arr.back() << std::endl;

    //値の書き換えもできる
    arr.back() = 50;

    std::cout << arr.back() << std::endl;

    std::cin.get();
}

これらは先頭/末尾要素の参照を返します。
front関数はarr[0]と同じことなのであまり使わないかもしれませんが、back関数は要素数に関わらず常に最後の要素にアクセスできるので便利です。

要素数が0のarrayに対するこれらの関数呼び出しは未定義です。

fill関数

fill関数は、すべての要素に同じ値をセットします。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 5> arr;

    arr.fill(10);

    //「10」を5個表示
    for (size_t i = 0; i < arr.size(); i++)
    {
        std::cout << arr[i] << "\n";
    }

    std::cin.get();
}

fill関数はarrayクラスのみに存在する関数で、他のコンテナ型には存在しません。
他のコンテナ型ではassign関数を使用します。

戻り値はありません。

swap関数

swap関数は、二つのarrayオブジェクトの要素をそっくり入れ替えます。


#include <iostream>
#include <array>

int main()
{
    std::array<int, 3> arr1{ 1, 2, 3 };
    std::array<int, 3> arr2{ 4, 5, 6 };

    arr1.swap(arr2);

    //4 5 6
    for (size_t i = 0; i < arr1.size(); i++)
    {
        std::cout << arr1[i] << " ";
    }
    std::cout << "\n";

    //1 2 3
    for (size_t i = 0; i < arr2.size(); i++)
    {
        std::cout << arr2[i] << " ";
    }

    std::cin.get();
}

戻り値はありません。

swap関数を使用するには、両方のarrayオブジェクトのデータ型と要素数が同じである必要があります。

また、swap関数は標準関数としても用意されており(std名前空間)、これは通常の配列にも利用することができます。
(utilityヘッダに定義)


#include <utility>

int arr1[] = { 1, 2, 3 };
int arr2[] = { 4, 5, 6 };

std::swap(arr1, arr2);