topazの遊び場

いろいろやってみる

そろそろC++やるぞパート11 配列5 ~ 配列の要素へのアクセスと添字演算子 ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

配列の初期化

基本型の配列の初期化はこちらで触れています。 自作クラス(参照型の配列)について、触れていきます。

こちらのPositionを使います。

class Position
{
public:
    Position(int x, int y)
    {
    }
};

int型の配列の場合はint ar[2];として宣言することが出来ましたが、コンストラクタがあるクラスの場合はできません。

int main()
{
    // error: no matching constructor for initialization of 'Position[3]'
    Position positions[3];

    return 0;
}

配列分の要素のコンストラクタが必要です。

Position positions[3]{
    Position(0, 0),
    Position(0, 0),
    Position(0, 0)};

ただ、コンストラクタを書かないで、デフォルトコンストラクタとする場合は、コンストラクタを書かずに配列の宣言ができます。

class Position
{
public:
    // default constructor
    // Position(int x, int y)
    // {
    // }
};
int main()
{
    Position positions[3];
    return 0;
}

クラスの配列の場合は、波括弧({ })か、for文回すかで初期化は必ずした方が良さそうです。

配列の要素へのアクセス

今まで[ ]を用いてアクセスしていましたが、改めて。配列添字演算子[ ])を使用すると配列の個々の要素にアクセスできます。 Pythonのように、[-1]で最後の要素にアクセスする機能はありません。

int ar[3]{3, 6, 9};
for (int i = 0; i < 3; i++)
{
    cout << ar[i] << endl;
}

添字なしで一次元配列の名前を指定すると、先頭の要素のポインターとして評価されます。

if (ar == &ar[0])
{
    cout << "Same Address" << endl;
}
// Output:
// Same Address

多次元配列でポインターを使用すると特定の列のみ抜き出すことができます。

int ar[3][2]{{3, 6},
                {1, 6},
                {2, 35}};
int *ar1Ptr = ar[1];
for (int i = 0; i < 2; i++)
{
    cout << ar1Ptr[i] << endl;
}
// Output:
// 1
// 6

ドキュメントを正確に読むと、先頭の要素のポインターとして『評価されます』。評価されます。と書いてあります。
配列の変数を右辺に置き、評価された後のポインターを、ポインターの変数に代入することは可能ですが、ポインターを代入することはできません。

int *ar1Ptr = ar[1];
// error: array type 'int[2]' is not assignable
ar[1] = ar1Ptr;

先ほど、Pythonのように-1で最後の要素へのアクセスはないと大ましたが、入れつの中の要素をポインターとして持つことで、負の数へのアクセスを実現できます。

int ar[5]{19, 17, 15, 13, 11};
int *arPtr = &ar[2];
cout << arPtr[-2] << endl;
cout << arPtr[-1] << endl;
cout << arPtr[0] << endl;
// Output:
// 19
// 17
// 15

演算子オーバーロード

他の演算子のように、添字演算子 ([]) は、ユーザーが再定義できます。 基本は、次のメソッドを使用して配列名と添字を組み合わせます。

*((array_name) + (subscript))

subscriptは添字という意味で、こちらに詳細が載っています。これらは同じ表現となります。

cout << *(ar + i) << endl;
cout << ar[i] << endl;
cout << i[ar] << endl;

こちらを参考にすると、

class DataStoreArray
{
public:
    DataStoreArray(std::size_t size) : mValueArray(new int[size])
    {
    }

    ~DataStoreArray()
    {
        delete[] mValueArray;
    }

    inline int operator[](std::size_t index) const
    {
        cout << "in" << endl;
        return mValueArray[index];
    }

    inline int &operator[](std::size_t index)
    {
        cout << "out" << endl;
        return mValueArray[index];
    }

private:
    int *mValueArray;
};

int main()
{
    DataStoreArray storage = DataStoreArray(10);
    storage[4] = 14;
    cout << storage[4] << endl;
    return 0;
}
// Output:
// 14

ただ、こちらは演算子オーバーロードの時に詳しく触れたいと思います。

参考リスト

感想

本日で配列は終わりになります。今までに4回
1. 導入と定数式
2. スタックとヒープ
3. 初期化と関数渡し
4. 多次元配列
今回で5回でした。途中途中で少し気になることの調査で脱線したものの、かなり理解できたと思います。
ただ初回に触れたように、std::arraystd::vectorがお勧めされており、こちらにも触れていきたいなと思います。

次回はポインターについて、触れていきたいと思います。