|
C++の壺・配列篇
C++の基本テクニックのTipsです。配列とは、同じデータ型の集合体です。配列篇では、配列の知識としくみ、基本的な使用方法を扱います。

配列は同じデータ型の要素を複数集めたものです。変数名の後ろに []をつけて宣言します。[]の中に要素数を入れます。 また、初期化するときは{}の中に、,で区切って要素を並べます。 この時、配列の要素は省略することもできます。不足する添え字の要素があれば、自動的に 0 が割り当てられます。 また、各要素には 添え字(インデックス) を使ってアクセスします。添え字は必ず 0 から始まり、要素数ー1 で終わります。
int n[10] ; // int型の要素を10個持った配列を宣言
int a[10] = {10, 20, 30, 40, 50, 60, 70, 80} ; // 配列の宣言と初期化(添え字は 0〜9)
int fig0 = a[0] ; // = 10
int fig1 = a[1] ; // = 20
int fig2 = a[7] ; // = 80
int fig3 = a[8] ; // = 0 (添え字8と9の要素は自動的に0で初期化)
| 配 列 | int a[10] = {10, 20, 30, 40, 50, 60, 70,} ; |
| 添え字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 要 素 | a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | a[9] |
| 要素の値 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 0 | 0 | 0 |
配列の個数は省略することもできます。この時は、初期化の際の要素数をもとに自動的にサイズが調整されます。 また、多次元にわたる配列も定義が可能です。array[2][3] とすると、要素数3の配列を2つ宣言することになります。初期化の際は、視認性をよくするために、次元グループごとに{}でくくることができます。要素数を省略する時は、先頭の要素数のみが省略可能です。
int m[] = {11, 22, 33, 44, 55, 66, 77} ; // 要素数を省略して宣言
int n[2][3] = { {10, 20, 30}, {70, 80, 90} } ; // 二次元配列(要素数3個の配列を2個)
// → int n[2][3] = {10, 20, 30, 70, 80, 90} ; としても同じ)
int a[][3] = { {10, 20, 30}, {111 ,222 333} } ; // 要素数を省略
int fig1 = a[0][0] ; // = 10
int fig2 = a[0][1] ; // = 20
int fig3 = a[1][0] ; // = 111
int fir4 = a[1][1] ; // = 222
| 二次元配列 | int a[2][4] = { {10, 20, 30, 40}, {60, 70, 80, 90} } ; |
| グループ | a[0][x] : {10, 20, 30, 40} | a[1][x] : {60, 70, 80, 90} |
| 要 素 | a[0][0] | a[0][1] | a[0][2] | a[0][3] | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
| 要素の値 | 10 | 20 | 30 | 40 | 60 | 70 | 80 | 90 |
配列の場合、添え字なしの配列名は先頭要素(添え字0の要素)のアドレスを指します。整数値を加算したり減算したりすることで、各要素の値やアドレスを表現することができます。
配 列 ポインタ | int a[7] = {11, 22, 33, 44, 55, 66, 77} ; int *p = a ; |
| 配列の要素 | a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] |
| 要素のアドレス | a | a+1 | a+2 | a+3 | a+4 | a+5 | a+6 |
| ポインタで要素の表現 | *p | *(p+1) | *(p+2) | *(p+3) | *(p+4) | *(p+5) | *(p+6) |
| ポインタでアドレスの表現 | p | p+1 | p+2 | p+3 | p+4 | p+5 | p+6 |
配列は、初期化の際とは異なり、そのままでは代入はできません。要素一つ一つに代入する必要があります。コピーをする時も同様に各要素ごとに値をコピーする必要があります。
// 配列の代入
#include <conio.h> // getch使用
int main()
{
int n[3];
// n[3] = {11, 22, 33}; → 不可
// 要素ごとに代入
n[0] = 11;
n[1] = 22;
n[2] = 33;
if (getch()) return 0;
} | // 配列のコピー
#include <conio.h> // getch使用
int main()
{
int n[3] = {11, 22, 33};
int a[3]; // a[3] = n[3]; → 不可
int i;
for (i=0; i<3; i++) {
a[i] = n[i]; // 要素ごとにコピー
}
if (getch()) return 0;
}
|
配列のサイズに値を設定する時、変数や変数の混じった式を使用することはできません。また、0 以下の値も設定できません。値が 1 以上となる定数または定数式であることが必要です。
// サイズに記号定数を代入
#include <conio.h>
#define SIZE 2
int main()
{
int n[SIZE+1]; // 要素数=3
n[0] = 11;
n[1] = 22;
n[2] = 33;
if (getch()) return 0;
} | // サイズにconst定数を代入
#include <conio.h>
int main()
{
const int SIZE = 4;
int n[SIZE-1]; // 要素数=3
n[0] = 11;
n[1] = 22;
n[2] = 33;
if (getch()) return 0;
}
|
【配列の引数】
配列を関数で使用する際、配列を引数(ひきすう)として取ることができます。この時、[]を添えて配列として記述することも、ポインタを使って記述することも可能です。以下の二つは同じ内容となります。
// 配列コピー関数
// dest...コピー先、src...コピー元、len...長さ
void acopy(int dest[], int src[], int len)
{
int i ;
for (i=0; i<len; i++) {
dest[i] = src[i] ;
}
} | // 配列コピー関数
// dest...コピー先、src...コピー元、len...長さ
void acopy(int *dest, int *src, int len)
{
int i ;
for (i=0; i<len; i++) {
*(dest+i) = *(src+i);
}
}
|
上記のように、C/C++ の配列の引数は、先頭要素のアドレスを渡すだけなので、別途長さ( len の部分)を指定する必要があります。この時、文字列のヌル文字のように、配列の終端を定数定義すると簡潔になることがあります。定数には、要素の値として使われることのない値を用います。ただし、変数側では、配列のサイズを、少なくとも 要素数+1 分を確保する必要があります。
#include <stdio.h> // printf使用
#include <conio.h> // getch使用
// 配列の終端を定義
#define EOA -1 // End Of Array
// 配列のコピー
void acopy(int *dest, int *src)
{
while (*src != EOA) {
*dest++ = *src++;
}
*dest = EOA; // 末尾にEOAを代入
} |
int main()
{
int n[4] = {11, 22, 33, EOA}; // コピー元
int a[4]; // コピー先
int i;
acopy(a, n); // 配列のコピー
// 表示
for (i=0; a[i] != EOA; i++) {
printf("%d, ", a[i]); // "11, 22, 33, "
}
if (getch()) return 0;
}
|
【多次元配列の引数】
引数が多次元配列の場合は次のように記述することができます。以下は二次元配列の例ですが、配列の初期化の時と同様に、最初の[]内のサイズは省略することができません。
// 二次元配列のコピー
// a1...コピー先、a2...コピー元、len...長さ
void acopy(int a1[][3], int a2[][3], int len)
{
int i, j;
for (i=0; i<len; i++) {
for (j=0; j<3; j++) {
a1[i][j] = a2[i][j]; // コピー
}
}
} |
// main関数
void main()
{
// コピー元
int n[2][3] = {{11,22,33}, {44,55,66}};
//コピー先
int a[2][3];
// コピーの実行
acopy(a, n, 2);
}
|
【戻り値としての配列】
関数では、配列を戻り値とする時も、アドレスを渡すことになります。この時、関数内の変数を返す時は、static キーワードをつけて静的変数にする必要があります。通常の変数だと、関数の終了と同時に破棄されてしまうためです。静的変数は、関数が終わっても保持され、通常は、プログラム終了時に破棄されます。 あるいは、右側の例のように、引数に格納先 (*dest) を用意しておく方法もあります。
#include <stdio.h>
#include <conio.h>
// 配列をコピーして返す
// src...コピー元
int *acopy(int src[])
{
static int dest[3];
int i;
for (i=0; i<3; i++) {
dest[i] = src[i];
}
return dest;
}
// main 関数
int main()
{
int n[3] = {11, 22, 33};
int *p = NULL;
p = acopy(n);
int i;
for (i=0; i<3; i++) {
printf("%d, ", *(p+i));
}
if (getch()) return 0;
} | #include <stdio.h>
#include <conio.h>
// 配列をコピーして返す
// dest...格納先、src...コピー元、len...長さ
int *acopy(int *dest, int *src, int len)
{
int i;
for (i=0; i<len; i++) {
*(dest+i) = *(src+i);
}
return dest;
}
// main 関数
int main()
{
int n[3] = {11, 22, 33};
int a[3];
int *p;
p = acopy(a, n, 3);
int i;
for (i=0; i<3; i++) {
printf("%d, ", *(p+i));
}
if (getch()) return 0;
}
|
配列は、通常、定数値をサイズに指定しますが、動的に配列を作成する時は、変数値を代入することができます。これにより、メモリの節約や、より柔軟な配列の操作が可能となります。 配列を動的に作成する時は、new 演算子を用います。また、new 演算子で作成されたオブジェクトは、自動的に破棄されません。 delete 演算子を用いて明示的に破棄しないと、メモリ上にデータが残ったままになってしまいますので注意が必要です。なお、配列の破棄の場合は、[] の記述が必要です。
・ 作成 : new データ型[サイズ];
・ 破棄 : delete [] データへのポインタ;
#include <stdio.h>
#include <conio.h>
// main 関数
int main()
{
int n = 10; // サイズ
int i;
int *p;
p = new int[n]; // 作成
for (i=0; i<n; i++) {
*(p+i) = i; // 0...9
}
for (i=0; i<n; i++) {
printf("%d, ", *(p+i));
}
delete [] p; // 破棄
if (getch()) return 0;
} | #include <stdio.h>
#include <conio.h>
// main 関数
int main()
{
int n = 26; // サイズ
int i;
char *p;
p = new char[n+1]; // 作成
for (i=0; i<n; i++) {
*(p+i) = 'A' + i; // 'A'...'Z'
}
*(p+i) = '\0'; // 末尾にヌル文字を追加
printf("%s\n", p);
delete [] p; // 破棄
if (getch()) return 0;
}
|
|