次の方法で共有


配列 (C++)

配列とは、連続したメモリ領域を占有する、同じ型のオブジェクトのシーケンスです。 従来の C スタイルの配列は、多くのバグの元となっていますが、依然として一般的です。古いコード ベースでは特にそうです。 最新の C++ では、このセクションで説明する C スタイルの配列を使用するかstd::array、代わりに使用std::vectorすることを強くお勧めします。 これらの標準ライブラリのタイプはどちらも、連続したメモリ ブロックとして要素を格納します。 ただし、より高い型安全性を提供し、シーケンス内の有効な位置を指し示す反復子をサポートします。 詳細については、コンテナーに関する記事を参照してください。

スタックの宣言

C++ の配列宣言では、配列のサイズは、一部の他の言語のように型名の後ではなく、変数名の後に指定されます。 次の例では、スタックに 1000 の double 型の配列を割り当てることを宣言します。 要素の数は、整数リテラルとして、または定数式として指定する必要があります。 これは、コンパイラが、割り当てるスタック領域の量を認識している必要があるためです。実行時に計算される値を使用することはできません。 配列内の各要素には、既定値 0 が割り当てられます。 既定値を割り当てない場合、各要素には最初に、そのメモリ位置にあるランダムな値が入ります。

    constexpr size_t size = 1000;

    // Declare an array of doubles to be allocated on the stack
    double numbers[size] {0};

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i-1] * 1.1;
    }

    // Access each element
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

配列内の最初の要素は、0 番目の要素です。 最後の要素は (n-1) 要素です。ここで、n は、配列に含めることができる要素の数です。 宣言内の要素の数は、整数型である必要があります。また、0 より大きい必要があります。 プログラムで (size - 1) より大きい値を添字演算子に渡すことが決してないようにする必要があります。

サイズ 0 の配列が有効になるのは、配列が struct または union の最後のフィールドであり、Microsoft 拡張機能が有効 (/Za または /permissive- が設定されていない) である場合に限られます。

スタック ベースの配列は、ヒープ ベースの配列よりも割り当てとアクセスが高速です。 ただし、スタック領域は制限されます。 配列要素の数を、スタック メモリが過剰に使用されるほど多くすることはできません。 どれほどの量が過剰かは、プログラムに応じて異なります。 プロファイル ツールを使用して、配列が大きすぎるかどうかを判断できます。

ヒープの宣言

スタックに割り当てるには大きすぎるか、コンパイル時にサイズが不明である配列を必要とすることがあります。 このような配列は、new[] 式を使用してヒープに割り当てることができます。 演算子は、最初の要素へのポインターを返します。 添字演算子は、スタック ベースの配列の場合と同様にポインター変数で機能します。 また、ポインターの算術演算を使用して、配列内の任意の要素にポインターを移動することもできます。 以下を確実に行う必要があります。

  • 配列が不要になったときにメモリを削除できるよう、元のポインター アドレスのコピーを常に保持する。
  • ポインター アドレスの増分または減分によって配列の範囲を超えることがないようにする。

次の例では、実行時に配列をヒープに定義する方法を示します。 添字演算子の使用と、ポインター算術演算の使用によって配列要素にアクセスする方法を示します。

void do_something(size_t size)
{
    // Declare an array of doubles to be allocated on the heap
    double* numbers = new double[size]{ 0 };

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i - 1] * 1.1;
    }

    // Access each element with subscript operator
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

    // Access each element with pointer arithmetic
    // Use a copy of the pointer for iterating
    double* p = numbers;

    for (size_t i = 0; i < size; i++)
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    // Alternate method:
    // Reset p to numbers[0]:
    p = numbers;

    // Use address of pointer to compute bounds.
    // The compiler computes size as the number
    // of elements * (bytes per element).
    while (p < (numbers + size))
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    delete[] numbers; // don't forget to do this!

}
int main()
{
    do_something(108);
}

配列の初期化

配列の初期化は、ループで一度に要素 1 つずつ、または 1 つのステートメントで行うことができます。 以下の 2 つの配列の内容は同じです。

    int a[10];
    for (int i = 0; i < 10; ++i)
    {
        a[i] = i + 1;
    }

    int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

関数に配列を渡す

スタック ベースまたはヒープ ベースの配列のどちらであっても、配列は関数に渡されるとき、最初の要素へのポインターとして渡されます。 ポインターには、他のサイズまたは型の情報が含まれています。 この動作は、ポインターへの減衰と呼ばれます。 配列を関数に渡すときはいつも、別のパラメーターで要素の数を指定する必要があります。 この動作は、配列が関数に渡される際に配列要素がコピーされないことも意味しています。 関数によって要素が変更されないようにするために、パラメーターを const 要素へのポインターとして指定します。

次の例では、配列と長さを受け取る関数を示します。 ポインターは、コピーではなく元の配列を指します。 パラメーターは const でないため、関数は配列要素を変更できます。

void process(double *p, const size_t len)
{
    std::cout << "process:\n";
    for (size_t i = 0; i < len; ++i)
    {
        // do something with p[i]
    }
}

配列パラメーター pconst として宣言および定義して、関数ブロック内で読み取り専用にします。

void process(const double *p, const size_t len);

同じ関数を、動作を変更せずに以下の方法で宣言することもできます。 配列はやはり、最初の要素へのポインターとして渡されます。

// Unsized array
void process(const double p[], const size_t len);

// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);

多次元配列

他の配列から生成された配列は、多次元配列です。 これらの多次元配列は、角かっこで囲まれた定数式を複数並べることで指定されます。 たとえば、次の宣言について考えます。

int i2[5][7];

以下の図に示すように、5 行と 7 列の 2 次元マトリックスの概念として並べられる、int 型の配列を指定しています。

Conceptual layout of a multidimensional array.

画像は、幅 7 セル、高さ 5 セルのグリッドです。 各セルには、セルのインデックスが含まれています。 最初のセルインデックスには 0,0 というラベルが付けられます。 その行の次のセルは 0,1 で、その行の最後のセルは 0,6 です。 次の行はインデックス 1,0 で始まります。 その後のセルのインデックスは 1,1 です。 その行の最後のセルは 1,6 です。 このパターンは、インデックス 4,0 で始まる最後の行まで繰り返されます。 最後の行の最後のセルのインデックスは 4,6 です。 :::image-end

初期化子リスト (「初期化子」で説明されているように) を持つ多次元配列を宣言できます。 このような宣言では、最初の次元の範囲を指定する定数式を省略できます。 次に例を示します。

// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

前の宣言は、3 行 x 4 列の配列を定義します。 行はファクトリを表し、列はファクトリの出荷先のマーケットを表します。 値は、ファクトリからマーケットへの輸送コストです。 配列の最初の次元は省かれますが、コンパイラは初期化子を調べることによってこれを入力します。

n 次元の配列型で間接演算子 (*) を使用すると、n–1 次元の配列が生成されます。 n が 1 の場合、スカラー (または配列要素) が生成されます。

C++ 配列は、行優先順で格納されます。 行優先は、最後の添字が最も速く変化することを意味します。

次に示すように、関数の宣言で、多次元配列における最初の次元の範囲指定を省略することもできます。

// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits>   // Includes DBL_MAX
#include <iostream>

const int cMkts = 4, cFacts = 2;

// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /
                  sizeof( double[cMkts] );

double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);

using namespace std;

int main( int argc, char *argv[] ) {
   double MinCost;

   if (argv[1] == 0) {
      cout << "You must specify the number of markets." << endl;
      exit(0);
   }
   MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
   cout << "The minimum cost to Market " << argv[1] << " is: "
       << MinCost << "\n";
}

double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
   double MinCost = DBL_MAX;

   for( size_t i = 0; i < cFacts; ++i )
      MinCost = (MinCost < TransportCosts[i][Mkt]) ?
         MinCost : TransportCosts[i][Mkt];

   return MinCost;
}
The minimum cost to Market 3 is: 17.29

FindMinToMkt 関数は、新しいファクトリを追加してもコード変更の必要はなく、再コンパイルするだけで済むように書かれています。

配列の初期化

クラス コンストラクターを持つオブジェクトの配列は、コンストラクターによって初期化されます。 配列内の要素よりも初期化子リスト内の項目の方が少ない場合、残りの要素には既定のコンストラクターが使用されます。 クラスに対して既定のコンストラクターが定義されていない場合は、完全な初期化子リストが必要です。つまり、配列の各要素に 1 つずつ初期化子が必要です。

2 つのコンストラクターを定義する Point クラスを考えます。

// initializing_arrays1.cpp
class Point
{
public:
   Point()   // Default constructor.
   {
   }
   Point( int, int )   // Construct from two ints
   {
   }
};

// An array of Point objects can be declared as follows:
Point aPoint[3] = {
   Point( 3, 3 )     // Use int, int constructor.
};

int main()
{
}

aPoint の最初の要素はコンストラクター Point( int, int ) を使用して構築されます。残りの 2 つの要素は既定のコンストラクターを使用して構築されます。

静的メンバー配列は、(const であるかどうかに関係なく) 定義内 (クラス宣言の外側) で初期化できます。 次に例を示します。

// initializing_arrays2.cpp
class WindowColors
{
public:
    static const char *rgszWindowPartList[7];
};

const char *WindowColors::rgszWindowPartList[7] = {
    "Active Title Bar", "Inactive Title Bar", "Title Bar Text",
    "Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
int main()
{
}

配列要素へのアクセス

配列添字演算子 ([ ]) を使用すると、配列の個々の要素にアクセスできます。 添字なしで 1 次元配列の名前を使用すると、配列の最初の要素へのポインターとして評価されます。

// using_arrays.cpp
int main() {
   char chArray[10];
   char *pch = chArray;   // Evaluates to a pointer to the first element.
   char   ch = chArray[0];   // Evaluates to the value of the first element.
   ch = chArray[3];   // Evaluates to the value of the fourth element.
}

多次元配列を使用すると、式でさまざまな組み合わせを使用できます。

// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {
   double multi[4][4][3];   // Declare the array.
   double (*p2multi)[3];
   double (*p1multi);

   cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.
   p2multi = multi[3];               // Make p2multi point to
                                     // fourth "plane" of multi.
   p1multi = multi[3][2];            // Make p1multi point to
                                     // fourth plane, third row
                                     // of multi.
}

上記のコードで、multidouble 型の 3 次元配列です。 p2multi ポインターは、サイズが 3 で型が double の配列を参照します。 この例では、配列が 1 つ、2 つ、および 3 つの添字と共に使用されています。 cout ステートメントのように、すべての添字を指定する方が一般的ですが、cout に続くステートメントのように、配列要素の特定のサブセットを選択した方が便利なこともあります。

添字演算子のオーバーロード

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

*((array_name) + (subscript))

ポインター型を含むすべての加算と同様に、スケーリングは型のサイズを調整するように自動的に実行されます。 結果の値は、array_name の原点からの n バイトではなく、配列の n 番目の要素です。 この変換の詳細については、「加法演算子」を参照してください。

同様に、多次元配列の場合は、次のメソッドを使用してアドレスが派生されます。

((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

式の配列

配列型の ID が sizeof、address-of (&)、または参照の初期化以外の式に出現すると、最初の配列要素へのポインターに変換されます。 次に例を示します。

char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;

ポインター psz は、配列 szError1 の最初の要素をポイントします。 配列はポインターとは異なり、変更可能な lvalue ではありません。 したがって、次の割り当ては正しくありません。

szError1 = psz;

関連項目

std::array