名前空間
変種
操作

配列宣言

提供: cppreference.com
< cpp‎ | language
 
 
 
 

配列型のオブジェクトを宣言します。

目次

[編集] 構文

配列宣言は宣言子が以下の形式を持つ単純宣言です。

noptr-declarator [ expr(オプション) ] attr(オプション) (1)
noptr-declarator - 任意の有効な宣言子。 ただし *&、または && で始まる場合は括弧で囲む必要があります。
attr(C++11) - オプショナルな属性のリスト。
expr - ゼロより大きな値に評価される整数定数式 (C++14未満) std::size_t 型の変換された定数式 (C++14以上)

T a[N]; 形式の宣言は T 型の連続的に確保された N 個のオブジェクトから構成される配列オブジェクトとして a を宣言します。 配列の要素は 0, …, N - 1 と番号付けられ、 a[0], …, a[N - 1] のように添字演算子 [] でアクセスできます。

配列は任意の基本型 (void を除く)、ポインタメンバへのポインタクラス列挙から、または他の境界が既知な配列 (この場合、配列は多次元であると言います) から構築できます。 別の言い方をすると、オブジェクト型と境界が未知な配列のみが配列型の要素型に使用できます。 要素型が不完全型の場合、その配列型も不完全型です。

参照の配列または関数の配列はありません。

配列型への (typedef またはテンプレート型操作を通した) cv 修飾の適用は、その修飾子をその要素型に適用しますが、要素が cv 修飾された型である配列型は、その同じ cv 修飾を持つとみなされます。

// a と b は同じ const 修飾された型「const char 5個の配列」です。
typedef const char CC;
CC a[5] = {}; 
typedef char CA[5];
const CA b = {};

new[] 式で使用されるときは、配列のサイズはゼロでも構いません。 そのような配列は要素を持ちません。

int* p = new int[0]; // p[0] や *p へのアクセスは未定義です。
delete[] p; // 後片付けはいつも通り必要です。

[編集] 代入

配列型のオブジェクトは全体として変更することはできません。 それらは左辺値である (すなわち配列のアドレスを取ることができる) けれども、代入演算子の左辺に現れることはできません。

int a[3] = {1, 2, 3}, b[3] = {4, 5, 6};
int (*p)[3] = &a; // OK、配列のアドレスを取ることはできます。
a = b;            // エラー、配列に代入することはできません。
struct { int c[3]; } s1, s2 = {3, 4, 5};
s1 = s2; // OK、暗黙に定義されたコピー代入演算子は
         // 配列型のデータメンバを代入できます。

[編集] 配列からポインタへの降格

配列型の左辺値および右辺値からポインタ型の右辺値への暗黙の変換が存在します。 これは配列の最初の要素を指すポインタを構築します。 この変換は配列は期待されないけれどもポインタが期待される文脈に配列が現れたときに常に用いられます。

#include <iostream>
#include <numeric>
#include <iterator>
 
void g(int (&a)[3])
{
    std::cout << a[0] << '\n';
}
 
void f(int* p)
{
    std::cout << *p << '\n';
}
 
int main()
{
    int a[3] = {1, 2, 3};
    int* p = a;
 
    std::cout << sizeof a << '\n'  // 配列のサイズを表示します。
              << sizeof p << '\n'; // ポインタのサイズを表示します。
 
    // 配列は受理されるけれどもポインタは受理されない場面では、配列だけが使用できます。
    g(a); // OK、この関数は配列を参照で取ります。
//  g(p); // エラー。
 
    for(int n: a)              // OK、配列は範囲 for ループで使用できます。
        std::cout << n << ' '; // 配列の要素を表示します。
//  for(int n: p)              // エラー。
//      std::cout << n << ' ';
 
    std::iota(std::begin(a), std::end(a), 7); // OK、 begin および end は配列を取ります。
//  std::iota(std::begin(p), std::end(p), 7); // エラー。
 
    // ポインタは受理されるけれども配列は受理されない場面では、どちらも使用できます。
    f(a); // OK、この関数はポインタを取ります。
    f(p); // OK、この関数はポインタを取ります。
 
    std::cout << *a << '\n' // 1つめの要素を表示します。
              << *p << '\n' // 同上。
              << *(a + 1) << ' ' << a[1] << '\n'  // 2つめの要素を表示します。
              << *(p + 1) << ' ' << p[1] << '\n'; // 同上。
}


[編集] 多次元配列

配列の要素型が別の配列であるとき、その配列は多次元であると言います。

// それぞれが int 3個の配列である2個の配列。
int a[2][3] = {{1, 2, 3},  // 行優先レイアウトの
               {4, 5, 6}}; // 2×3の行列として見ることもできます。

配列からポインタへの降格が適用されるとき、多次元配列はその最初の要素を指すポインタ (例えば、その最初の行、または最初の面へのポインタ) に変換されます。 配列からポインタへの降格は一度のみ適用されます。

int a[2];            // int 2個の配列。
int* p1 = a;         // a は a の最初の要素を指すポインタに降格します。
 
int b[2][3];         // int 3個の配列2個の配列。
// int** p2 = b;     // エラー、 b は int** に降格しません。
int (*p2)[3] = b;    // b は b の最初の3要素の行を指すポインタに降格します。
 
int c[2][3][4];      // int 4個の配列3個の配列2個の配列。
// int*** p3 = c;    // エラー、 c は int*** に降格しません。
int (*p3)[3][4] = c; // c は c の最初の3×4要素の面を指すポインタに降格します。

[編集] 境界が未知な配列

配列の宣言で expr が省略された場合、宣言された型は「T の境界が未知な配列」です。 これは不完全型の一種です。 ただし集成体初期化付きの宣言で使用されたときは除きます。

extern int x[];      // x の型は「int の境界が未知な配列」です。
int a[] = {1, 2, 3}; // a の型は「int 3個の配列」です。

配列の要素が境界が未知な配列であることはできないため、多次元配列は最初以外の次元に未知な境界を持つことはできません。

extern int a[][2]; // OK、 int 2個の配列の境界が未知な配列。
extern int b[2][]; // エラー、配列は不完全な要素型を持てません。

境界が未知な配列への参照およびポインタは形成できますが、境界が既知な配列への配列およびポインタから初期化または代入することはできません。 ちなみに、 C プログラミング言語では、境界が未知な配列へのポインタは境界が既知な配列へのポインタと互換性があり、そのため双方向に変換可能かつ代入可能です。

extern int a1[];
int (&r1)[] = a1;  // OK。
int (*p1)[] = &a1; // OK。
int (*q)[2] = &a1; // エラー (C では OK)。
 
int a2[] = {1, 2, 3};
int (&r2)[] = a2;  // エラー。
int (*p2)[] = &a2; // エラー (C では OK)。

境界が未知な配列へのポインタは、ポインタ算術に使用できず、添字演算子の左に使用することはできませんが、逆参照することはできます。 関数の仮引数で境界が未知な配列へのポインタおよび参照を使用することはできません。 (C++14未満)

[編集] 配列の右辺値

配列は関数から値返ししたりほとんどのキャスト式の対象とすることはできませんが、波括弧初期化された関数形式のキャストを用いた配列の一時オブジェクトの構築のために型エイリアスを用いて配列の prvalue を形成することはできます。

クラスの prvalue と同様に、配列の prvalue は評価されたときに一時具体化によって xvalue に変換されます。 (C++17以上)

配列の xvalue は、クラスの rvalue の配列メンバをアクセスすることによって、または std::move やその他のキャストや右辺値参照を返す関数呼び出しを用いることによって、直接形成できます。

#include <iostream>
#include <type_traits>
#include <utility>
 
void f(int (&&x)[2][3])
{
    std::cout << sizeof x << '\n';
}
 
struct X
{
    int i[2][3];
} x;
 
template<typename T> using identity = T;
 
int main()
{
    std::cout << sizeof X().i << '\n';           // 配列のサイズ。
    f(X().i);                                    // OK、 xvalue に束縛します。
//  f(x.i);                                      // エラー、 lvalue に束縛できません。
 
    int a[2][3];
    f(std::move(a));                             // OK、 xvalue に束縛します。
 
    using arr_t = int[2][3];
    f(arr_t{});                                  // OK、 prvalue に束縛します。
    f(identity<int[][3]>{{1, 2, 3}, {4, 5, 6}}); // OK、 prvalue に束縛します。
 
}

出力:

24
24
24
24
24

[編集] 関連項目

配列宣言C言語リファレンス