配列

配列の生成

decl-specifier identifier []
decl-specifier identifier [ constant-expression ]

decl-specifier identifier [][ constant-expression ] ...
decl-specifier identifier [ constant-expression ][ constant-expression ] ...
配列 (C++) | MSDN
int p[10];

要素の数は定数式で指定しなければなりません。

const int num1 = 10;
int p1[num1]; // OK

int num2 = 10;
int p2[num2]; // C2131 式は定数に評価されませんでした (expression did not evaluate to a constant)

これを変数で指定するならば、new演算子で生成します。

int num2 = 10;
int* p2 = new int[num2];

// 配列を使用する処理

delete[] p2; // 不要になったならば、明示的に解放する

作成時に指定した要素数は固定のため、これを可変としたければvectorを用います。

初期化 (initialization)

初期値を与える場合には、要素数を指定する必要はありません。

int p[] = { 1, 2, 3 };

要素数を指定しても良いですが、与えられた要素の数が指定の要素数を超えると、エラーとなります。

int p1[3] = { 1, 2, 3 }; // OK
int p2[2] = { 1, 2, 3 }; // エラー

一方で指定の要素数に満たないと、残りの要素は既定値で初期化されます。

int p1[3] = { 1, 0, 0 }; // 1, 0, 0 が格納される
int p2[3] = { 1 };       // 1, 0, 0
int p3[3] = {};          // 0, 0, 0
int p4[3]{};             // 0, 0, 0

char ch[2] = {}; // '\0', '\0'
int* ip[2] = {}; // 0x00000000, 0x00000000
配列の初期化 | MSDN

既定値

Visual C++では初期化していない領域に、次の様に値が割り当てられます

int

int* i = new int[3];
i-3,8
インデックス 値 (10進)
0x00 (i[-3]) 0x00000000 ※1 0 int
0x01 (i[-2]) 0x00000042 ※1 66 int
0x02 (i[-1]) 0xfdfdfdfd -33686019 int
0x03 (i[0]) 0xcdcdcdcd -842150451 int
0x04 (i[1]) 0xcdcdcdcd -842150451 int
0x05 (i[2]) 0xcdcdcdcd -842150451 int
0x06 (i[3]) 0xfdfdfdfd -33686019 int
0x07 (i[4]) 0x00000000 ※1 0 int
※1 場合によって異なる
  • 0xfdfdfdfd … "no man's land" guard bytes before and after allocated heap memory
  • 0xcdcdcdcd … uninitialized heap memory
メモリ割り当ての既定値

double

double* d = new double[3];
d-3,8
インデックス
0x00 (d[-3]) 2.121995790965e-314#DEN ※1 double
0x01 (d[-2]) 1.185757550019e-322#DEN ※1 double
0x02 (d[-1]) -7.8459077160212854e+298 ※1 double
0x03 (d[0]) -6.2774385622041925e+66 double
0x04 (d[1]) -6.2774385622041925e+66 double
0x05 (d[2]) -6.2774385622041925e+66 double
0x06 (d[3]) 2.105352686232e-314#DEN ※1 double
0x07 (d[4]) 0.00000000000000000 ※1 double
※1 場合によって異なる
(int*)d-3,11
インデックス
0x00 0x00000000 ※1 int
0x01 0x00000043 ※1 int
0x02 0xfdfdfdfd int
0x03 0xcdcdcdcd int
0x04 0xcdcdcdcd int
0x05 0xcdcdcdcd int
0x06 0xcdcdcdcd int
0x07 0xcdcdcdcd int
0x08 0xcdcdcdcd int
0x09 0xfdfdfdfd int
0x0a 0x00000000 ※1 int
※1 場合によって異なる

要素数の取得

ある配列pがあるとき、

  • sizeof( p ) … 配列全体のバイト数
  • sizeof( *p ) … 配列の1つの要素のバイト数

であることから、sizeof( p ) / sizeof( *p )で要素数が求まります。

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

int s1 = sizeof( p );                // 20
int s2 = sizeof( *p );               // 4
int s3 = sizeof( p ) / sizeof( *p ); // 5

一方で、関数の引数に渡されるのは配列ではなくポインタであるため、関数の引数に対してはこの方法は適用できません。

int main()
{
    int p[] = { 1,2,3,4,5 }; // pは配列の先頭要素へのポインタ
    Func(p);
}

void Func( int p[] ) // pはポインタ
{
    int s1 = sizeof( p );                // 4 (int*のバイト数)
    int s2 = sizeof( *p );               // 4
    int s3 = sizeof( p ) / sizeof( *p ); // 1
}

配列とポインタ

配列の名前は、その配列の先頭要素へのポインタです。よって次のp1とp2は同一のアドレスを示します。

int* p1 = a;
int* p2 = &a[0];

また配列の要素はアドレス順に配置されるため、ポインタを1つ進めた位置の値は、[1]とした位置の値と同一です。

int x1 = *(a + 1);
int x2 = a[1];

int p[3]としたとき、この配列へは下表のようにアクセスできます。

記述 意味
&p int(*)[3] 配列全体を指すポインタ
p int* 配列の要素p[0]を指すポインタ
*&p
p+1 int* 配列の要素p[1]を指すポインタ
p+2 int* 配列の要素p[2]を指すポインタ
*p int 配列の要素p[0]の値
p[0]
*(p+1) int 配列の要素p[1]の値
p[1]

このような配列を示すポインタは、Visual Studioでは「p,n」の形式でウォッチ ウィンドウでデバッグできます。

多次元配列 (multidimensional arrays)

多次元配列は、配列の配列として表現されます。

int p2[3][2];

初期値を与える場合には、最初の要素数以外は省略できません。

int p2[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
int p3[][3][2] = { { { 1, 2 }, { 3,  4 }, {  5,  6 } },
                   { { 7, 8 }, { 9, 10 }, { 11, 12 } } };

要素へのアクセス

for( int i = 0; i < 3; i++ )
{
    for( int k = 0; k < 2; k++ )
    {
        p2[i][k] = 10 * i + k;
    }
}

多次元配列は、メモリ上では1次元の連続した領域として確保されます。たとえば3×2の配列は、次のように格納されます。

要素 [0][0] [0][1] [1][0] [1][1] [2][0] [2][1]
1次元 要素が2個ずつ 要素が2個ずつ 要素が2個ずつ
2次元 配列が3個

int t[3][2]としたとき、この配列へは下表のようにアクセスできます。

記述 意味
&t int(*)[3][2] 配列t全体を指すポインタ
t int(*)[2] 配列の要素t[0]を指すポインタ
t+0
t+1 int(*)[2] 配列の要素t[1]を指すポインタ
*t int * 配列の要素t[0][0]を指すポインタ
t[0]
*(t+1) int * 配列の要素t[1][0]を指すポインタ
t[1]
**t
(ダブルポインタ)
int 配列の要素t[0][0]の値
*t[0]
t[0][0]
*(*t+1) int 配列の要素t[0][1]の値
*(*(t+1)) int 配列の要素t[1][0]の値
*t[1]
t[1][0]
*(*(t+1)+1) int 配列の要素t[1][1]の値

配列のコピー

memcpy()では、バッファ間でバイトをコピーできます。

void *memcpy(
    void *dest,      // コピー先のバッファ
    const void *src, // コピー元のバッファ
    size_t count     // コピーする文字数
);
memcpy、wmemcpy | MSDN

戻り値はdestの値です。

int p1[] = { 1,2,3 };
int p2[3];
memcpy(p2, p1, sizeof(int) * 3);

配列コピー時に犯しやすい誤りに注意する ――C/C++セキュアコーディング入門(6) (1/2):CodeZine(コードジン) 富樫 一哉 (2010/02/15)

これはstd::copy()で、次のようにも記述できます。

int p1[] = { 1,2,3 };
int p2[3];
std::copy(p1, p1 + 3, p2);
Is there a function to copy an array in C/C++? - Stack Overflow

この関数は次のように宣言されています。

template<class InputIterator, class OutputIterator>
    OutputIterator copy(
        InputIterator _First,
        InputIterator _Last,
        OutputIterator _DestBeg
        );
copy - <algorithm> functions | MSDN

型キャスト

std::copy()で異なる型へコピーすることで、異なる型に変換できます。

double p1[] = { 1.0,1.5,2.0 };
int p2[3];

std::copy(p1, p1 + 3, p2);

// p2[0] … 1
// p2[1] … 1
// p2[2] … 2
c++ typecast array - Stack Overflow

文字列リテラル (string literal)

文字列リテラルとは、ダブルクォートで囲まれた文字の連続です。その型は適切な数のconst文字の配列であり、たとえば"ABC"const char[4]です。

次の4つは同一の文字列に初期化されます。

char s1[4] = { 'A','B','C','\0' };
char s2[4] = "ABC";

char s3[] = { 'A','B','C','\0' };
char s4[] = "ABC";

要素数を指定するならば、終端のnullに注意します。

char str1[4] = "ABC"; // OK
char str2[3] = "ABC"; // error C2117: 'str2': 指定された配列には、初期化子が多すぎます。

文字列リテラルの変更

Cとの互換性のために、文字列リテラルはchar*に代入できます。しかし実体はconst char*であるため、

char* p = "ABC";
p[0] = 'X'; // アクセス違反
*p = 'X';   // アクセス違反

とすると、処理系によっては実行時エラーとなります。ですので変更する必要があるならば、

char p[] = "ABC";
p[0] = 'X';

のように、配列に代入するようにします。文字列リテラルの変更 - 文字列リテラルと文字リテラル (C++) | MSDN

ワイド文字列リテラル

ワイド文字リテラルは、L'a'のように「L」という接頭辞をつけて表現します。

wchar_t* str = L"SAMPLE";

TEXTマクロ

「L」接頭辞を付けるかどうかを、処理系に合わせて適用できます。

void TEXT( LPTSTR string );
TEXT macro (Windows) | MSDN

このマクロはWinNT.hで次のように定義されており、#defineディレクティブで"UNICODE"と定義されているときに有効となります。

#ifdef  UNICODE                     // r_winnt

#define TEXT(quote) __TEXT(quote)   // r_winnt

#define __TEXT(quote) L##quote      // r_winnt

ところでマクロの引数であるLPTSTRとは、UNICODEが定義されていればwchar_t*

typedef LPWSTR PTSTR, LPTSTR;

typedef _Null_terminated_ WCHAR *NWPSTR, *LPWSTR, *PWSTR;

typedef wchar_t WCHAR;    // wc,   16-bit UNICODE character

定義されていなければchar*となります。

typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;

typedef _Null_terminated_ CHAR *NPSTR, *LPSTR, *PSTR;

typedef char CHAR;

エスケープされない文字列

エスケープが必要な文字を、エスケープせずに記述できます。

R"(文字列)"
char s1[] = R"("A")"; // "A"
// s1[0] 0x22 '\"'
// s1[1] 0x41 'A'
// s1[2] 0x22 '\''
// s1[3] 0x00 '\0'

char s2[] = R"(\r\n)";            // \r\n
char s3[] = R"(c:\dir\text.txt)"; // c:\dir\text.txt
表記
char (UTF-8) u8R"( ... )"
wchar_t LR"( ... )"
wchar16_t (UTF-16) uR"( ... )"
wchar32_t (UTF-32) UR"( ... )"
String and Character Literals (C++) | MSDN

参考

参考書

関数への渡し方

void Func1(int p[], int size)
{
    for (int i = 0; i < size; i++) p[i] = i;
}

引数として渡されるのは実際にはポインタのため、次のように記述しても同じです。

void Func2(int *p, int size)
{
    for (int i = 0; i < size; i++) p[i] = i * 2;
}

これらの関数は、次のように呼び出せます。

int p[3];
Func1(p, 3);
Func2(p, 3);

参考

参考書

Microsoft Learnから検索