配列

導入

配列とは、同じ型の値のセットを順番にまとめて格納したものです。配列は変数名の後の角括弧で表され、角括弧内は配列のメンバーの数です。

int スコア[100];

上の例では、100 個のメンバーを含む配列 scores を宣言しており、各メンバーの型は int です。

配列を宣言するときは、配列のサイズを指定する必要があることに注意してください。

配列のメンバーには '0' から始まる番号が付けられるため、配列 'scores[100]' は 0 番目のメンバーから 99 番目のメンバーまで始まり、最後のメンバーの番号は配列の長さより '1' 小さくなります。

メンバーは、配列名の後に角かっこ内の番号を指定することで参照できます。この方法で場所に値を割り当てることもできます。

スコア[0] = 13;
スコア[99] = 42;

上記の例では、配列 scores の最初と最後の位置に値を割り当てます。

存在しない配列メンバーを参照した場合 (つまり、範囲外の配列にアクセスした場合)、エラーは報告されないため、十分に注意する必要があることに注意してください。

int スコア[100];

スコア[100] = 51;

上の例では、配列 scores には 100 個のメンバーしかないため、位置 scores[100] は存在しません。ただし、この場所を参照してもエラーは報告されず、正常に実行されます。その結果、「scores」の直後のメモリ領域に値が割り当てられますが、これは実際には他の変数の領域であるため、他の変数の値は変数の値が無意識に変更されます。これにより、検出が困難なエラーが簡単に発生する可能性があります。

配列は、中括弧を使用して各メンバーに値を代入して宣言することもできます。

int a[5] = {22, 37, 3490, 18, 95};

中括弧を使用して値を割り当てる場合は、配列の宣言時に値を割り当てる必要があります。そうでないと、コンパイル中にエラーが報告されます。

int a[5];
a = {22, 37, 3490, 18, 95}; // エラーレポート

上記のコードでは、配列 a が宣言され、中括弧で割り当てられているため、エラーが発生します。

エラーの理由は、C 言語では、配列変数を一度宣言すると、その変数が指すアドレスを変更できないと規定されているためです。詳細は後ほど説明します。同じ理由で、配列の割り当て後に中かっこを使用して値を変更することはできません。

int a[5] = {1, 2, 3, 4, 5};
a = {22, 37, 3490, 18, 95}; // エラーレポート

上記のコードでは、配列 a が割り当てられた後、中括弧を使用してそれを再割り当てすることはできません。

代入に中括弧を使用する場合、中括弧内の値は配列の長さを超えることはできません。そうでない場合は、コンパイル中にエラーが報告されます。

中括弧内の値が配列のメンバー数より小さい場合、割り当てられていないメンバーは自動的に '0' に初期化されます。

int a[5] = {22, 37, 3490};
// と同等
int a[5] = {22, 37, 3490, 0, 0};

配列全体のすべてのメンバーをゼロに設定したい場合、最も簡単な書き方は次のとおりです。

int a[100] = {0};

配列を初期化するときに、メンバーのどの位置に値を割り当てるかを指定できます。

int a[15] = {[2] = 29、[9] = 7、[14] = 48};

上記の例では、配列の位置 2、9、14 に値が割り当てられ、他の位置の値は自動的に 0 に設定されます。

指定位置の割り当ては順序どおりである必要はありません。次の記述方法は上記の例と同等です。

int a[15] = {[9] = 7、[14] = 48、[2] = 29};

指定位置への割り当てと順次割り当てを組み合わせて使用​​できます。

int a[15] = {1, [5] = 10, 11, [10] = 20, 21}

上記の例では、No.0、No.5、No.6、No.10、No.11 に値が割り当てられています。

C 言語では、角括弧内の配列メンバーの数を省略できます。この場合、配列の長さは中括弧内の値の数に基づいて自動的に決定されます。

int a[] = {22, 37, 3490};
// と同等
int a[3] = {22, 37, 3490};

上記の例では、中括弧内の値の数に基づいて、配列「a」の長さが「3」と決定されます。

メンバ数を省略した場合、指定位置への代入も併用する場合、配列長は指定位置の最大値に1を加えた長さになります。

int a[] = {[2] = 6, [9] = 12};

上記の例では、配列 'a' の最大指定位置は '9' であるため、配列の長さは 10 になります。

配列の長さ

sizeof 演算子は、配列全体の長さをバイト単位で返します。

int a[] = {22, 37, 3490};
int arrLen = sizeof(a);

上記の例では、「sizeof」によって返される配列「a」のバイト長は「12」です。

配列メンバーはすべて同じ型であり、各メンバーのバイト長も同じであるため、配列内のメンバーの数は、配列全体のバイト長を特定の配列メンバーのバイト長で割ることによって取得できます。

サイズ(a) / サイズ(a[0])

上記の例では、「sizeof(a)」は配列全体のバイト長、「sizeof(a[0])」は配列メンバーのバイト長、除算は配列のメンバーの数です。

なお、sizeofの戻り値のデータ型はsize_tなので、sizeof(a) / sizeof(a[0])のデータ型もsize_tとなります。 printf() のプレースホルダーには、%zd または %zu を使用します。

int x[12];

printf("%zu\n", sizeof(x)); // 48
printf("%zu\n", sizeof(int)); // 4
printf("%zu\n", sizeof(x) / sizeof(int));

上記の例では、 sizeof(x) / sizeof(int) で配列メンバーの数 12 を取得できます。

多次元配列

C 言語では、複数次元の配列を宣言できます。たとえば、2 次元配列には 2 つの角括弧を使用します。

int ボード[10][10];

上の例では、1 次元に 10 個のメンバー、2 次元に 10 個のメンバーを持つ 2 次元配列を宣言しています。

多次元配列は、上位次元の各メンバー自体が配列であると理解できます。たとえば、上記の例では、1 次元の各メンバー自体が 10 個のメンバーを含む配列であるため、2 次元配列全体には合計 100 個のメンバーがあります (10 x 10 = 100)。

3 次元配列は、3 つの角括弧などを使用して宣言されます。

int c[4][5][6];

2 次元配列の各メンバーを参照する場合は、2 つの角括弧を使用して両方の次元を指定する必要があります。

ボード[0][0] = 13;
ボード[9][9] = 13;

board[0][0]board[0, 0] として書くことはできないことに注意してください。0, 0 はカンマ式で 2 番目の値を返すため、board[0, 0]board[0] と同等です。

1 次元配列と同様に、多次元配列の各次元の最初のメンバーには「0」から始まる番号が付けられます。

多次元配列では中括弧を使用して、すべてのメンバーに一度に値を割り当てることもできます。

int a[2][5] = {
  {01234}、
  {56789}
};

上の例では、「a」は 2 次元配列です。この代入方法は、最初の次元の各メンバーを配列として記述することと同じです。この書き方では各メンバーに値を割り当てる必要がなく、不足しているメンバーは自動的に「0」に設定されます。

多次元配列では、初期化と代入の位置を指定することもできます。

int a[2][2] = {[0][0] = 1、[1][1] = 2};

上記の例では、[0][0][1][1]の値を指定し、その他の位置は自動的に0に設定されます。

配列の次元数に関係なく、「a[0][0]」の後に「a[0][1]」が続き、「a[0][1]」が続きます。 a [1][0] などによって。したがって、多次元配列は単層の中括弧を使用して代入することもできます。次のステートメントは上記の代入ステートメントと完全に同等です。

int a[2][2] = {1, 0, 0, 2};

可変長配列

配列を宣言するときは、定数を使用するだけでなく、配列の長さとして変数を使用することもできます。これは可変長配列 (VLA) と呼ばれます。

int n = x + y;
int arr[n];

上記の例では、配列 arr は可変長配列です。その長さは変数 n の値に依存するため、コンパイラは実行時にのみ n が何であるかを知ることができます。 。

可変長配列の基本的な特徴は、配列の長さは実行時にのみ決定できることです。その利点は、プログラマが開発中に配列の推定長を任意に指定する必要がなく、プログラムが実行時に配列に正確な長さを割り当てることができることです。

実行時に長さを決定する必要がある配列は、可変長配列です。

int i = 10;

int a1[i];
int a2[i + 5];
int a3[i + k];

上記の例では、コードを実行することで 3 つの配列の長さを知る必要があります。コンパイラーはそれらの長さを知らないため、それらはすべて可変長配列です。

可変長配列は多次元配列とともに使用することもできます。

int m = 4;
int n = 5;
int c[m][n];

上記の例では、「c[m][n]」は 2 次元の可変長配列です。

配列のアドレス

配列は、同じ型の連続して格納された一連の値です。開始アドレス (最初のメンバーのメモリ アドレス) が取得される限り、他のメンバーのアドレスを計算できます。以下の例を参照してください。

int a[5] = {11, 22, 33, 44, 55};
int* p;

p = &a[0];

printf("%d\n", *p); // 「11」を出力します。

上記の例では、「&a[0]」は配列「a」の最初のメンバー「11」のメモリ アドレスであり、配列全体の開始アドレスでもあります。次に、このアドレス (*p) から、最初のメンバーの値 11 を取得できます。

配列の開始アドレスは一般的な演算なので、「&array[0]」と書くと便利です。つまり、配列名は配列名になります。最初のメンバー ( array[0]) ポインタを指します。

int a[5] = {11, 22, 33, 44, 55};

int* p = &a[0];
// と同等
int* p = a;

上記の例では、&a[0] は配列名 a に相当します。

この場合、配列名を関数に渡すことは、ポインター変数を渡すことと同じです。関数内では、このポインター変数を通じて配列全体を取得できます。

この関数はパラメータとして配列を受け取り、関数のプロトタイプは次のように記述できます。

// 書き方その1
int sum(int arr[], int len);
//書き方2
int sum(int* arr, int len);

上の例では、整数配列を渡すことは、整数ポインタを渡すことと同じです。配列記号 [] とポインタ記号 * は交換可能です。次の例は、配列ポインターを介してメンバーを合計する例です。

int sum(int* arr, int len) {
  int i;
  int 合計 = 0;

  // 配列には 10 個のメンバーがあると仮定します
  for (i = 0; i < len; i++) {
    合計 += arr[i];
  }
  合計を返します。
}

上記の例では、関数に渡されるのはポインタ arr (配列の名前でもあります) であり、配列の各メンバーはポインタを通じて取得され、合計が取得されます。

* および & 演算子は多次元配列でも使用できます。

int a[4][2];

// a[0][0]の値を取得します
*(a[0]);
// と同等
**a

上の例では、a[0] 自体がポインターなので、2 次元配列の最初のメンバー a[0][0] を指します。したがって、*(a[0]) は、a[0][0] の値を取り出します。 「**a」については、「a」に対して「*」演算を2回行うことを意味し、1回目は「a[0]」、2回目は「a[0]」を取り出します。 [0]。同様に、二次元配列の &a[0][0]*a` と等価です。

配列名が指すアドレスは変更できないことに注意してください。配列を宣言すると、コンパイラは自動的にメモリ アドレスを配列に割り当てます。このアドレスは配列名にバインドされているため、次のコードはエラーを報告します。

int ints[100];
ints = NULL; // エラーレポート

上記の例では、配列名を再割り当てして元のメモリ アドレスを変更すると、エラーが報告されます。

これにより、配列名を別の配列名に割り当てることもできなくなります。

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

//書き方その1
int b[5] = // エラー;

//書き方2
int b[5];
b = a; // エラーレポート

上記 2 つの書き込み方法では、配列 b のアドレスが変更されるため、エラーが発生します。

配列ポインタの加算と減算

C 言語では、配列名を加算および減算できます。これは、配列メンバー間を行き来すること、つまり、あるメンバーのメモリー・アドレスから別のメンバーのメモリー・アドレスに移動することに相当します。たとえば、「a + 1」は次のメンバーのアドレスを返し、「a - 1」は前のメンバーのアドレスを返します。

int a[5] = {11, 22, 33, 44, 55};

for (int i = 0; i < 5; i++) {
  printf("%d\n", *(a + i));
}

上の例では、ポインタを移動することによって配列が走査され、「a + i」の各サイクルは次のメンバーのアドレスを指します。このアドレスの値は次のようになります。 a[i ] と同等です。配列の最初のメンバーの場合、*(a + 0) (つまり、*a) は a[0] と同等です。

配列名とポインタは同等であるため、次の方程式が常に成り立ちます。

a[b] == *(a + b)

上記のコードでは、配列メンバーにアクセスする 2 つの方法が示されています。1 つは角括弧 a[b] を使用する方法、もう 1 つはポインタ *(a + b) を使用する方法です。

ポインタ変数 p が配列のメンバーを指す場合、 p++ は次のメンバーを指すことと同じです。 このメソッドは配列を走査するためによく使用されます。

int a[] = {11, 22, 33, 44, 55, 999};

int* p = a;

while (*p != 999) {
  printf("%d\n", *p);
  p++;
}

上記の例では、変数 p が次のメンバーを指すように p++ が使用されています。

なお、配列名が指すアドレスは変更できないので、上の例では「a」を直接インクリメントすることはできません。つまり、「a++」の書き方が間違っており、「a」のアドレスはポインタ変数 p に代入し、p をインクリメントします。

配列の走査は通常、配列の長さを比較することによって行われますが、配列の開始アドレスと終了アドレスを比較することによっても行うことができます。

int sum(int* start, int* end) {
  int 合計 = 0;

  while (開始 < 終了) {
    合計 += *開始;
    開始++;
  }

  合計を返します。
}

int arr[5] = {20, 10, 5, 39, 4};
printf("%i\n", sum(arr, arr + 5));

上記の例では、「arr」は配列の開始アドレス、「arr + 5」は終了アドレスです。開始アドレスが終了アドレスより小さい限り、配列の末尾に到達していないことを意味します。

次に、配列の減算を通じて、2 つのアドレス間に配列メンバーがいくつあるかを知ることができます。配列の長さを計算する関数を自分で実装するには、以下の例を参照してください。

int arr[5] = {20, 10, 5, 39, 88};
int* p = arr;

while (*p != 88)
  p++;

printf("%i\n", p - arr);

上記の例では、配列の開始アドレスから配列メンバーのアドレスを減算することで、現在のメンバーと開始アドレスの間にメンバーが何個あるかを知ることができます。

多次元配列の場合、配列ポインターの加算と減算は次元ごとに異なる意味を持ちます。

int arr[4][2];

// ポインタは arr[1] を指します
arr + 1;

// ポインタは arr[0][1] を指します
arr[0] + 1

上の例では、「arr」は 2 次元配列であり、「arr + 1」はポインタを 1 次元配列の次のメンバーである「arr[1]」に移動します。 1 次元の各メンバー自体には別の配列が含まれているため、つまり arr[0] は 2 次元配列へのポインタなので、 arr[0] + 1 はポインタを 2 次元配列に移動することを意味します。 2 次元配列の次のメンバー、つまり arr[0][1]

同じ配列の 2 つのメンバーへのポインターが減算されると、それらの間の距離が返されます。

int* p = &a[5];
int* q = &a[1];

printf("%d\n", p - q);
printf("%d\n", q - p); // -4

上記の例では、変数 'p' と 'q' はそれぞれ配列の位置 5 と位置 1 へのポインタであり、それらの減算は 4 または -4 に等しくなります。

配列のコピー

配列名はポインターであるため、配列をコピーする場合、単に配列名をコピーすることはできません。

int* a;
int b[3] = {1, 2, 3};

a = b;

上記の書き込み方法の結果は、配列 'b' を配列 'a' にコピーするのではなく、'a' と 'b' が同じ配列を指すようにすることです。

配列をコピーする最も簡単な方法は、ループを使用して配列要素を 1 つずつコピーすることです。

for (i = 0; i < N; i++)
  a[i] = b[i];

上記の例では、配列 'b' のメンバーを配列 'a' に 1 つずつコピーすることで配列の代入が行われます。

もう 1 つの方法は、memcpy() 関数 (ヘッダー ファイル string.h で定義されている) を使用して、配列が配置されているメモリを直接コピーすることです。

memcpy(a, b, sizeof(b));

上記の例では、配列 'b' が配置されているメモリが配列 'a' にコピーされます。この方法は、配列メンバーをループしてコピーするよりも高速です。

関数パラメータとして

パラメータ配列を宣言する

配列が関数のパラメーターとして使用される場合、通常、配列名と配列の長さが同時に渡されます。

int sum_array(int a[], int n) {
  // ...
}

int a[] = {3, 5, 7, 3};
int sum = sum_array(a, 4);

上の例では、関数 sum_array() の最初のパラメータは配列そのもの、つまり配列名で、2 番目のパラメータは配列の長さです。

配列名はポインターであるため、配列名のみが渡された場合、関数は配列の開始アドレスのみを認識し、終了アドレスは認識しないため、配列の長さも同様に渡す必要があります。

関数のパラメーターが多次元配列の場合、最初の次元の長さをパラメーターとして関数に渡すことができる場合を除き、他の次元の長さを関数の定義に書き込む必要があります。

int sum_array(int a[][4], int n) {
  // ...
}

int a[2][4] = {
  {1234}、
  {891011}
};
int sum = sum_array(a, 2);

上記の例では、関数 sum_array() のパラメータは 2 次元配列です。最初のパラメータは配列そのもの (a[][4]) です。このとき、最初の次元の長さは 2 番目のパラメータとして関数に渡されるため、記述する必要はありません。 2 番目の次元の長さを「4」と書く必要があります。

関数内で取得されるのは配列の開始アドレス「a」と1次元目のメンバー数「2」だけだからです。配列の終了アドレスを正しく計算したい場合は、最初の次元の各メンバーの長さ (バイト単位) も知っておく必要があります。 int a[][4] と書くと、コンパイラは最初の次元の各メンバー自体が 4 つの整数を含む配列であることを認識するため、各メンバーのバイト長は 4 * sizeof(int )` になります。

パラメータとしての可変長配列

関数の引数として可変長配列を使用する場合は、書き方が少し異なります。

int sum_array(int n, int a[n]) {
  // ...
}

int a[] = {3, 5, 7, 3};
int sum = sum_array(4, a);

上の例では、配列 a[n] は可変長配列であり、その長さは変数 n の値に依存し、実行時にのみ知ることができます。したがって、変数 n がパラメーターとして使用される場合、配列 a[n] の長さが実行時に決定できるように、順序は可変長配列の前になければなりません。そうしないとエラーが発生します。報告される。

関数プロトタイプではパラメータ名を省略できるため、可変長配列のプロトタイプでは、変数名の代わりに * を使用することも、変数名を省略することもできます。

int sum_array(int, int [*]);
int sum_array(int, int []);

可変長関数のプロトタイプを記述する上記の 2 つの方法はどちらも正当です。

可変長配列を関数のパラメーターとして使用する利点の 1 つは、多次元配列のパラメーター宣言で後続の次元を省略できることです。

//原文
int sum_array(int a[][4], int n);

// 可変長配列の書き方
int sum_array(int n, int m, int a[n][m]);

上記の例では、関数 sum_array() のパラメータは多次元配列になっており、本来の書き方では 2 次元目の長さを宣言する必要があります。しかし、可変長配列の書き込み方法を使用すると、2 次元の長さをパラメータとして関数に渡すことができるため、宣言する必要がありません。

パラメータとしての配列リテラル

C 言語では、配列リテラルをパラメータとして関数に渡すことができます。

// 配列変数をパラメータとして使用する
int a[] = {2, 3, 4, 5};
int sum = sum_array(a, 4);

// パラメータとして配列リテラル
int sum = sum_array((int []){2, 3, 4, 5}, 4);

上記の例では、2 つの書き方は同等です。 2 番目の書き方では、配列変数の宣言を省略し、配列リテラルを関数に直接渡します。 {2, 3, 4, 5} は配列値のリテラル値です。 (int []) は強制型変換に似ており、この値のセットを理解する方法をコンパイラーに指示します。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0